LIBRERIAS

CARGA DE DATOS

df_artist <- read.csv("data/df_artist_sin_duplicados.csv")
 df_charts_raw <- read.csv("data/df_charts_sin_duplicados.csv")
df_audio_features_raw <- read.csv("data/audio_features_plano_sin_duplicados.csv")
df_lyrics <- read.csv("data/df_lyrics.csv")
 

Corrección duplicados

# DF listo para el join con chrats
df_audio_features <- df_audio_features_raw %>% 
  group_by(track_name, external_urls_spotify) %>% 
  mutate(artist_all = paste(artist_name, collapse = ",|,")) %>%
  ungroup() %>% 
  mutate(artist_key = sub(",|,.*", "", artist_all)) %>% 
  dplyr::select(artist_name, artist_all, artist_key, everything(.)) %>% 
  distinct(artist_key, external_urls_spotify, .keep_all = T) %>% 
  as.data.frame()

Creacion cant_markets

contar_market <- function(x){
q <- length(unlist(strsplit(x, split = ",")))
return (q)
  }
df_audio_features$cant_markets <- sapply(df_audio_features[,"markets_concat"], contar_market)

Vectores de features

#features var continuos
features_continuas <- c('acousticness', 'danceability', 'duration_ms', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness',   'tempo', 'valence', 'cant_markets')

#features var_ categóricas
features_categoricas <- c('explicit', 'key_name', 'mode_name', "key_mode")

Imputacion de loudness

fit <- lm(loudness~energy+acousticness, data=df_audio_features)

modelo <- fit$coefficients

df_audio_features$loudness_reg_imp <- df_audio_features$loudness

X <- df_audio_features[df_audio_features$loudness>0, c('energy', "acousticness")]

df_audio_features$loudness_reg_imp[df_audio_features$loudness>0] <- modelo[1]+modelo[2]*X[,1]+modelo[3]*X[,2]

summary(df_audio_features[,c("loudness", "loudness_reg_imp")])

summary(fit)

#graficos con loudness con imputacion
par(mfrow = c(2,1)) 
hist(df_audio_features[,'loudness_reg_imp'], main='loudness', xlab="")
#hist(sqrt(df_audio_features[,'loudness_reg_imp']), main= 'loudness_sqrt', xlab="")
boxplot(df_audio_features[,'loudness_reg_imp'], horizontal = T)
#boxplot(sqrt(df_audio_features[,'loudness_reg_imp']), horizontal = T)

CHARTS: agregación de features

#metrica de popularidad
df_charts <- df_charts_raw %>% 
  group_by(Artist, Track_Name) %>%
  dplyr:: summarise(semanas_sum = as.double(n()),
            streams_sum = (sum(Streams, na.rm = T)/10^6 ),
            streams_min = (min(Streams)/10^6 ),
            streams_max = (max(Streams)/10^6 ),
            streams_avg = (mean(Streams)/10^6),
            position_avg = mean(Position, na.rm = T),
            position_median = median(Position, na.rm = T),
            position_min = min(Position), 
            position_max = max(Position)) %>% 
  ungroup() %>% 
  mutate(popularidad = as.numeric(streams_sum*semanas_sum/position_avg) )

library(reshape2)
ggplot(melt(df_charts[,3:ncol(df_charts)]), aes(value))+
  geom_histogram()+
  facet_wrap(~variable , scales = "free")

RIGTH JOIN audio_features Y charts

#Armamos un join para tener una tabla de charts con las caracteristicas de las canciones
# deberian quedar 22993 filas completas
join_audio_charts <- df_audio_features %>% 
  select("artist_name","artist_all","artist_key",
         "track_name", "external_urls_spotify", "album_name", "album_release_year",
         all_of(features_continuas), all_of(features_categoricas)) %>% 
  right_join( df_charts,# %>%
               by = c(
                 "track_name" = "Track_Name", 
                      "artist_key" ="Artist"))

#HAY CHARTS QUE NO TIENEN FEATURES. HAY QUE TENERLO EN CUENTA PARA EL ANÁLISIS
library(mice)
md.pattern(join_audio_charts, rotate.names = TRUE)

HISTOGRAMAS Y BARPLOTS DE VARIABLES


##histograma de las variables continuas de audio_features

for (i in features_continuas){

  hist(df_audio_features[,i], main = paste("Histograma de", i, "(all data)"), xlab = i)
  abline(v = mean(df_audio_features[,i], na.rm = TRUE) , col="red")
  abline(v = median(df_audio_features[,i], na.rm = TRUE) , col="blue")
  legend("topright", legend = c("Media", "Mediana"), col=c("red", "blue"), lty =1)

}

#divido los features por su distribución
features_continuas_media <- c('danceability', 'tempo', 'valence')

features_continuas_mediana <- c('acousticness', 'duration_ms', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets')


##histograma de las variables continuas de charts
for (i in c(features_continuas)){

  hist(join_audio_charts[,i], main = paste("Histograma de", i,  "(charts)"), xlab = i)
  abline(v = mean(join_audio_charts[,i], na.rm = TRUE) , col="red")
  abline(v = median(join_audio_charts[,i], na.rm = TRUE) , col="blue")

}

#divido features de charts según su distribución
audio_charts_continuas_media <- c('duration_ms', 'valence')

audio_charts_continuas_mediana <- c('danceability', 'acousticness', 'tempo', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets', "Streams")


##medidas resumen y barplots de las variables categoricas audio_features
for(i in features_categoricas){

  barplot(sort(table(df_audio_features[,i]),decreasing = T), las=2, 
          main = paste("Barplot de", i, "(all data)"))
  # pie(table(df_features_categoricos[,i]))
}



##medidas resumen y barplots de las variables categoricas join_audio_charts

for(i in features_categoricas){
  barplot(table(join_audio_charts[,i]), las=2,
          main = paste("Barplot de", i, "(charts)")
          )
  # pie(table(df_features_categoricos[,i]))
}

hist(df_audio_features$instrumentalness)

SESGO DE VARIABLES

Boxplots Variables Numéricas sin filtrar outliers

par(mfrow=c(4,3))
for (feature in features_continuas){
  boxplot(df_audio_features[,feature], las=2, horizontal=T, main=feature)
}

Con excepción de valence el resto de las features poseían cierto sesgo. Se decidió transformar las variables que mayor sesgo poseían: duration_ms, instrumentalness, liveness, speechiness como método de corregir la distribución y achicar la cantidad de outliers. La variable loudness_reg_imp no fue modificada debido a que al ser negativa

Transformaciones

Transformación logarítmica

# "danceability,tempo,valence,acousticness,duration_ms,energy,instrumentalness,liveness,speechiness,cant_markets"

#sesgos d las variables                                                   
sort(apply(df_audio_features[,features_continuas], MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)} ))

variables_sesgo <- unlist(strsplit("acousticness,duration_ms,instrumentalness,liveness,speechiness,cant_markets,energy", ","))

df_sesgadas <- df_audio_features[,variables_sesgo]

logaritmo_ajustado = function(x,delta){
  if (x==0.0){
    return(log(0.00+delta, base = 10))
  }else{
    return(log(x, base = 10))
  }
}
delta <- 10^(-6)

df_sesgadas_log_adjust <- data.frame(apply(df_audio_features[,variables_sesgo], MARGIN = c(1,2), 
                                           function(x) logaritmo_ajustado(x,delta)))

ggplot(reshape::melt(df_sesgadas), aes(value))+
  geom_histogram()+
  facet_wrap(~variable)

ggplot(reshape::melt(df_sesgadas_log_adjust), aes(value))+
  geom_histogram()+
  facet_wrap(~variable)


####################################################
# names(df_sesgadas_log_adjust) <- paste(names(df_sesgadas), "_log", sep="")
# names(df_sesgadas_log_adjust) <- names(df_sesgadas)
# df_datos <- cbind(df_sesgadas, df_sesgadas_log_adjust)

a <- df_sesgadas
b <- df_sesgadas_log_adjust
names(b) <- paste(names(df_sesgadas), "_log", sep="")
merged <- cbind(a,b)

merged <- merged[, order(names(merged))]

round((
  apply(
  merged, MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)}
        )
          ),2)
#histogramas de vbles transformadas con logaritmo
# transformacion <- c('instrumentalness','loudness','liveness','speechiness', 'duration_ms')

par(mfrow=c(3,5))
for (feature in variables_sesgo){
  hist(df_audio_features[,feature], main=feature)
}

for (feature in variables_sesgo){
  hist(unlist(lapply(df_audio_features[,feature], function(x) logaritmo_ajustado(x,delta))), main=paste(feature,"log", sep="_"))
}

Transformacion inversa raiz cuadrada


inv_sqrt_ajustada = function(x, delta){
  if (x==0.0){
    return(1/sqrt(x+0.000001))
  }else{
    return(1/sqrt(x))
  }
}
delta <- sqrt(10^(-6))


par(mfrow=c(3,5))
for (feature in variables_sesgo){
  hist(df_audio_features[,feature], main=feature)
}

for (feature in variables_sesgo){
  hist(unlist(lapply(df_audio_features[,feature], function(x) inv_sqrt_ajustada(x,delta))), main=paste(feature,"inv_sqt", sep="_"))
}

df_sesgadas_inv_sqrt <- data.frame(apply(df_audio_features[,variables_sesgo], MARGIN = c(1,2), 
                                           function(x) inv_sqrt_ajustada(x,delta)))
#nuevos sesgos con inversa raiz cuadrada
a <- df_sesgadas
b <- df_sesgadas_inv_sqrt
names(b) <- paste(names(df_sesgadas), "_invsqrt", sep="")
merged <- cbind(a,b)

merged <- merged[, order(names(merged))]

round((
  apply(
  merged, MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)}
        )
          ),2)


b <- df_sesgadas_inv_sqrt[,c("cant_markets", "energy", "instrumentalness", "liveness" , "speechiness")]
names(b) <- paste(names(b), "_invsqrt", sep="")

par(mfrow=c(1,2))
ggplot(data=reshape::melt(b), aes(value))+
  geom_histogram(bins = 5)+facet_wrap(~variable, scales = "free")

ggplot(data=reshape::melt(a), aes(value))+
  geom_histogram(bins = 5)+facet_wrap(~variable, scales = "free")

Sesgo DF charts

df_ses_chart <- df_charts %>% 
  ungroup() %>% 
  select( "Track_Name", "Artist", 
                       "streams_avg", "position_avg",
                       "semanas_sum", "popularidad")



df_ses_chart_log <- data.frame(apply(df_ses_chart[,3:6], MARGIN = c(1,2), 
                                           function(x) logaritmo_ajustado(x,delta)))

cat("originales\n")
#sesgos d las variables                                                   
(apply(df_ses_chart[,3:6], MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)} ))

cat("\ntransformadas\n")
(apply(df_ses_chart_log, MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)} ))


ggplot(reshape2::melt(df_ses_chart[,3:6]), aes(value))+
  geom_histogram()+
  facet_wrap(~variable, scales = "free")+
  labs(title = "Histogramas originales")

ggplot(reshape::melt(df_ses_chart_log), aes(value))+
  geom_histogram()+
  facet_wrap(~variable, scales = "free")+
  labs(title = "Histogramas transformados")
df_ses_chart_inv_sqrt <- data.frame(apply(df_ses_chart[,3:6], MARGIN = c(1,2), 
                                           function(x) inv_sqrt_ajustada(x,delta)))

cat("\ntransformadas\n")
(apply(df_ses_chart_inv_sqrt, MARGIN = 2, function(x){ (3* (mean(x,na.rm = T)-median(x, na.rm = T)))/sd(x, na.rm = T)} ))


charts_sin_sesgo <- cbind(df_ses_chart[,-c(3,5)], 
     "semanas_sum" =df_ses_chart_log[,c("semanas_sum")],
     "streams_avg" = df_ses_chart_inv_sqrt[,c("streams_avg")])

Normalizacion

Audio features

#join entre variables transformadas y resto features
audio_sin_sesgo <- df_audio_features %>% 
  select("artist_name", "artist_key",
         "track_name",
         all_of(features_continuas), all_of(features_categoricas)) %>%
  select(!variables_sesgo) 


audio_ft_to_discretize <- cbind(audio_sin_sesgo, df_sesgadas_log_adjust[,c("acousticness", "duration_ms")],
                           df_sesgadas_inv_sqrt[,c("instrumentalness", "cant_markets",
                                                   "liveness" , "speechiness")]) %>%
    select(-c( "key_name", "explicit", "mode_name" ,"key_mode")) %>% 
   group_by(artist_name, artist_key, track_name)  %>% 
  distinct(.keep_all = T)
  
#normalizacion de todas las vbles
scale_vble <- function(x){
  (x - mean(x, na.rm = T))/sd(x, na.rm = T)
}

audio_ft_to_discretize_norm <- audio_ft_to_discretize %>% 
  # mutate_all(scale)
  mutate_all(scale_vble)#
ggplot(data= melt(audio_ft_to_discretize, id.vars = c("artist_name", "artist_key", "track_name")), aes(value))+
         geom_histogram()+
  facet_wrap(~variable, scales = "free")

ggplot(data= melt(audio_ft_to_discretize_norm, id.vars = c("artist_name", "artist_key", "track_name")), aes(value))+
         geom_histogram()+
  facet_wrap(~variable, scales = "free")

Charts

charts_sin_sesgo_norm <- charts_sin_sesgo %>%
  rename(track_name = Track_Name, artist = Artist) %>% 
  ungroup() %>% 
  select(-c(track_name, artist)) %>%
  mutate_all(function(x) {scale_vble(x)})
  # mutate_at(., .vars = vars(-group_cols("Track_Name", "Artist")), function(x) {scale_vble(x)})

charts_sin_sesgo_norm <- cbind(charts_sin_sesgo[, c("Artist", "Track_Name")], charts_sin_sesgo_norm )

Discretización

Discretización

Audio Features


#funciones
cut_median <- function(x){
  cut(x, 
      breaks = c(0,median(x)/2,
                 median(x),
                 quantile(x,probs = 0.75),
                 Inf), 
      include.lowest = T,
      labels=c("Baja","Media","Alta", "Muy alta") )
  
}

cut_binary <- function(x){
  cut(x,
      breaks = c(min(x,na.rm = T),
                 median(x),
                 Inf), 
      include.lowest = T,
      labels=c("Baja","Alta"))
}

cut_cuantile <- function(x){
  cut(x, 
      breaks = quantile(x), 
      include.lowest = T,
      labels=c("Baja","Media","Alta", "Muy alta") )
}


#select data
df_audio_ft_select <-df_audio_features %>% 
  select(artist_name, artist_key, track_name, features_continuas, -loudness,loudness = loudness_reg_imp ) %>%
  group_by(track_name, artist_key, artist_name) %>%
  summarise_all(function(x) mean(x, na.rm = T)) %>% 
  left_join(df_audio_features %>% 
               select(artist_name, artist_key, track_name, explicit, mode_name) %>% 
               distinct(artist_name, artist_key, track_name, .keep_all = T) )
              

#creacion columnas
#by median
df_audio_ft_select$acousticness_cat <- cut_median(df_audio_ft_select$acousticness)
df_audio_ft_select$duration_ms_cat <- cut_median(df_audio_ft_select$duration_ms)
df_audio_ft_select$liveness_cat <-cut_median(df_audio_ft_select$liveness)
df_audio_ft_select$speechiness_cat <- cut_median(df_audio_ft_select$speechiness)

#by binary
df_audio_ft_select$instrumentalness_cat <- cut_binary(df_audio_ft_select$instrumentalness)
df_audio_ft_select$loudness_cat <- cut_binary(df_audio_ft_select$loudness)

#by cunatile
df_audio_ft_select$tempo_cat <- cut_cuantile(df_audio_ft_select$tempo)
df_audio_ft_select$valence_cat <- cut_cuantile(df_audio_ft_select$valence)
df_audio_ft_select$danceability_cat <- cut_cuantile(df_audio_ft_select$danceability)

#cant markets
df_audio_ft_select$cant_markets_cat <- cut(df_audio_ft_select$cant_markets, 
                                           breaks = c(0, 70, 110, Inf), 
                                           labels = c("Baja","Media","Alta"))

#true categories vbles
df_audio_ft_select$explicit_cat <- ifelse(df_audio_ft_select$explicit ==TRUE, "Si", "No")
df_audio_ft_select$mode_name_cat <- df_audio_ft_select$mode_name

# filtro
df_audio_ft_cat <- df_audio_ft_select %>% 
  select( artist_name, artist_key, track_name, contains("_cat")  )


x <-  df_audio_ft_cat <- df_audio_ft_select %>% 
  select( contains("_cat")  )

for(i in names(x) ){
  barplot(table(x[,i]), las=2,
          main = paste("Barplot de", i, "(charts)") )

   # cat(table(  x[,i] ))
   }

Charts

df_charts_sel <- df_charts %>% 
  ungroup() %>% 
  select( "Track_Name", "Artist", 
                       "streams_avg", "position_median",
                       "semanas_sum", "popularidad")

ggplot(reshape2::melt(df_charts_sel[,3:6]), aes(value))+
  geom_histogram()+
  facet_wrap(~variable, scales = "free")+
  labs(title = "Histogramas originales")


df_charts_sel$streams_avg_cat = cut_cuantile(df_charts_sel$streams_avg)
df_charts_sel$popularidad_cat = cut_cuantile(df_charts_sel$popularidad)

df_charts_sel$position_median_cat = cut(df_charts_sel$position_median,
                                        breaks = quantile(df_charts_sel$position_median), 
                                        include.lowest = T,
                                        labels=c( "Muy alta", "Alta", "Media", "Baja"))

df_charts_sel$semanas_sum_cat = cut(df_charts_sel$semanas_sum,
                                    breaks = c(0, 2,4,13, Inf),
                                    include.lowest = T,
                                    labels=c("Baja","Media","Alta", "Muy alta"))


# filtro
df_charts_cat <- df_charts_sel %>% 
  select( Artist,  Track_Name, contains("_cat")  )

Viejo

#prueba fallida
audio_ft_to_discretize_norm %>% 
  ungroup() %>% 
  group_by(artist_name, track_name, artist_key) %>% 
  mutate_all( function(x){ cut(x, quantile(x, probs = seq(0, 1, 0.25
                                                          ), na.rm = T,
                                           # ) , labels = c("Muy Alta","Alta" ,"Media", "Baja"
                                           # ) , labels = c("Muy Alta","Alta" ,"Media", "Baja"
                                           ) , labels = c("Muy Alta" ,"Alta", "Media", "Baja"
                                                          )
                               ) }
              )
  


# Discretizaciones basadas en Quantile (FUNCIONA)

library(R.oo)

cols <- names(audio_ft_to_discretize_norm)[!names(audio_ft_to_discretize_norm) %in% c("artist_name","artist_key", "track_name")]
df_num = audio_ft_to_discretize_norm[,cols]

df_cat <-  audio_ft_to_discretize_norm[, c("artist_name","artist_key", "track_name")]
for(i in seq_along(df_num) ){

  breaks =unique(quantile(df_num[[i]], probs = seq(0, 1, 0.33) ,na.rm = T))
  # breaks =unique(quantile(df_num[[i]], probs = seq(0, 1, 0.25)))
  # breaks =quantile(df_num[[i]] , names = FALSE)

  label = intToChar(65:(63+length(breaks)))
  # label = c("Muy Alta", "Alta", "Media", "Baja")

  x <-   cut(df_num[[i]], breaks=breaks,
            labels = label )
            # labels  = c("Muy Alta","Alta" ,"Media", "Baja") )

  df_cat <-  cbind(df_cat,  x )
  }

# seteo de nombres
names(df_cat) <- names(audio_ft_to_discretize_norm)

# audio_ft_to_discretize_norm  %>% filter(track_name =="Juice WRLD Speaks From Heaven - Outro")
# df_cat  %>% filter(track_name =="Baby")
# df_cat  %>% filter(track_name =="goodbye")
# df_cat  %>% filter(track_name =="DÁKITI")
# audio_ft_to_discretize_norm  %>% filter(track_name =="Rule The World (feat. Ariana Grande)")
# df_charts  %>% filter(Track_Name =="Rule The World (feat. Ariana Grande)")

audio_ft_to_discretize_norm  %>%
  group_by(artist_name, track_name) %>%
  arrange(streams_avg)
  # slice(which.max(danceability))
  # filter(danceability == max(danceability, na.rm =T))

#analisis de missing values
mice::md.pattern(df_cat, plot = T, rotate.names = T)

sum(!complete.cases(df_cat))

summary(VIM::aggr(df_cat, sortVar= T,plot=F))

# JOIN FINAL

#falta transformar df_charts !!!
join_audio_charts <- audio_ft_to_discretize_norm %>% 
  right_join( charts_sin_sesgo_norm ,
               by = c("track_name" = "Track_Name", 
                      "artist_key" ="Artist")) %>% 
  select(-c("artist_key", "key_name", "explicit", "mode_name" ,"key_mode")) %>% 
  group_by(artist_name, track_name) %>%
  summarise_all( function(x) mean(x, na.rm= T)) %>%
  ungroup() %>% 
  filter(!is.na(artist_name)) %>% 
  group_by(artist_name, track_name)# %>%
  # mutate_all(function(x) scales::rescale(x, to=c(0,1)))
  # mutate_all(function(x)  (x-min(x, na.rm = T)) / (max(x, na.rm = T)-min(x, na.rm = T)) ) 
  

Normalizacion vieja

Z-Score de Variables que “tienden a la normal”


################################

## FILTRAMOS OUTLIERS POR Z-SCORE para 'danceability', 'tempo', 'valence'

##############################

#z-score para variables que tienden a la normal
#filtro features numericos 

#divido los features por su distribución
features_continuas_media <- c('danceability', 'tempo', 'valence')
df_audio_features_zscore_media <- df_audio_features[,features_continuas_media]

#normalizo z score con las variables que tienden a la normal

zscore_cols <- c()
for(col in names(df_audio_features_zscore_media)){
  name_col <- paste('zscore_',col, sep = "")
  zscore_cols <- append(zscore_cols, name_col)
  media <-  mean(df_audio_features_zscore_media[,col])
  stdv <- sd(df_audio_features_zscore_media[,col])
  df_audio_features_zscore_media[,name_col] <- (df_audio_features_zscore_media[,col] - media)/stdv
  }

par(mfrow=c(1,length(zscore_cols)))
lapply(zscore_cols, function(col) boxplot(df_audio_features_zscore_media[,col],xlab=col))

Analisis de Z-Score por variable

Danceability

#variable: danceability

umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_danceability> umbral_zscore) | (df_audio_features_zscore_media$zscore_danceability< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, danceability ) %>%
  arrange(-danceability)

Tempo

#variable: Tempo

umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_tempo> umbral_zscore) | (df_audio_features_zscore_media$zscore_tempo< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, tempo ) %>%
  arrange(-tempo)

Valence

#variable: valence
umbral_zscore <- 3
conditions <- (df_audio_features_zscore_media$zscore_valence> umbral_zscore) | (df_audio_features_zscore_media$zscore_valence< -1*umbral_zscore)
df_audio_features[conditions,] %>%
  select(album_name,artist_name, valence ) %>%
  arrange(-valence)

Z-Score Modificado de Variables Asimetricas

################################

## FILTRAMOS OUTLIERS POR Z-SCORE MODIFICADO para 'acousticness', 'duration_ms', 'energy',  'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets'

##############################

features_continuas_mediana <- c('acousticness', 'duration_ms', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'cant_markets')

df_audio_features_zscore_mediana <- df_audio_features[,features_continuas_mediana]



zscoremodif_cols <- c()
for(col in names(df_audio_features_zscore_mediana)){
  name_col <- paste('zscoremodif_',col, sep = "")
  zscoremodif_cols <- append(zscoremodif_cols, name_col)
  med = median(df_audio_features_zscore_mediana[,col], na.rm = T)
  MAD = median(abs(df_audio_features_zscore_mediana[,col] - med), na.rm = T)
  df_audio_features_zscore_mediana[, name_col] <- 0.6745 * (df_audio_features_zscore_mediana[,col] - med) / MAD
}


par(mfrow=c(4,2))
lapply(zscoremodif_cols, function(col) boxplot(df_audio_features_zscore_mediana[,col],xlab=col, horizontal = T))

Revisión Variable Instrumentalness

instrumentalness <- c("instrumentalness", "zscoremodif_instrumentalness") 

x <- df_audio_features$instrumentalness

n_interv <- 10


intervalos <- round(seq(0,max(x),by=(max(x)-min(x))/n_interv),2)

labs <- c()
for (i in 1:n_interv){
lab <- paste(intervalos[i],intervalos[i+1], sep='\n')
labs <- append(labs, lab)
    
}

bins <- cut(x, n_interv, include.lowest = TRUE, labels = labs)

barplot(table(bins))

Hacemos K-means para poder discretizar la variable.

sse <- c()
for (k in 2:6){
  clusters <- kmeans(df_audio_features$instrumentalness,centers = k, iter.max = 10, nstart = k)
  sse <- append(sse, clusters$tot.withinss)
}

plot(2:6,sse, type = 'l', xlab='Cantidad de Clusters', ylab='Suma Error Cuadrático')

#k=3 
clusters3 <- kmeans(df_audio_features$instrumentalness,centers = 3, iter.max = 10, nstart = 3)

df_audio_features$clusters <- factor(clusters3$cluster)

lev <- levels(df_audio_features$clusters)

labs <- c()
for (i in lev){
  min <- min(df_audio_features$instrumentalness[df_audio_features$clusters==i])
  max <- max(df_audio_features$instrumentalness[df_audio_features$clusters==i])
  lab <- paste(min,max, sep=' - ')
  labs <- append(labs, lab)
}

labs

# barplot(table(factor(clusters3$cluster)), labels = labs)

#prueba igal de transformacion y test de normalidad

join_audio_charts[1:5,"acousticness"]^2

library(nortest)

log10(df_chart_w_lyrics$acousticness)

for (i in features_continuas){
   x <- log10(df_chart_w_lyrics[,i])
   x <- shapiro.test(x)
   z <- x$p.value
  print(z)
  }

|# LYRICS

Filtro por idioma

# tabla contingencia de idiomas
# idiomas = textcat(df_lyrics_unicas$lyrics)
# sort(table(idiomas), decreasing = T)

limpieza ingles

STOPWORDS

Inglés


#El word_count_df se realiza con Python (ejecutar en_lyrics_word_coun)
word_counts_df <- read.csv("data/en_lyrics_word_count.csv")
x <- 1:nrow(word_counts_df)
f <- word_counts_df$counts
plot(x,f, type="l",
          log = 'xy',
          col="blue",
          lwd=3,
          ylab = "Frecuencia Absoluta",
          xlab = "",
          main="Frecuencia de Palabras - Ley de Zipf")

library(stopwords)

Attaching package: 㤼㸱stopwords㤼㸲

The following object is masked from 㤼㸱package:tm㤼㸲:

    stopwords
threshold <- 1000

x <- as.vector(word_counts_df[word_counts_df$counts>threshold,]$word)

not_remove_list = c("bitch","fuck","love","baby","nigga","feel", "girl", "shit")

top_stopwords <- setdiff(x, not_remove_list)

smart_stopwords <- stopwords("en", source = "smart")
my_eng_stopwords <- unique(append(smart_stopwords, top_stopwords))

Español

my_spa_stopwords <- unique(text_cleaning(stopwords("es", source = "stopwords-iso"), language = "es"))  

Borro Stopwords

del_stopwords = function(txt, stopword_list){
  remove_regex = paste("\\b(", paste(stopword_list, collapse = "|"),")\\W", sep="")
  txt = gsub(remove_regex, " ", txt)
  txt = gsub("\\W+\\b", " ", txt)
  return(txt)
}

spa_lyrics$sin_stopwords <- del_stopwords(spa_lyrics$lyrics_cleaning, my_spa_stopwords)
en_lyrics$sin_stopwords <- del_stopwords(en_lyrics$lyrics_cleaning, my_eng_stopwords)

DICCIONARIO DE MALAS PALABRAS

Español

#Diccionario español
malas_palabras_1 <- read_csv("data/malas_palabras.txt", 
    col_names = FALSE)

-- Column specification ---------------------------------------------------------------------------
cols(
  X1 = col_character()
)
malas_palabras_2 <- read_csv("data/malas_palabras_translate.txt", 
    col_names = FALSE)

-- Column specification ---------------------------------------------------------------------------
cols(
  X1 = col_character()
)
malas_palabras_3 <- read_csv("data/malas_palabras_wiki.txt", 
    col_names = FALSE) %>% 
  select(X1)

-- Column specification ---------------------------------------------------------------------------
cols(
  X1 = col_character(),
  X2 = col_character(),
  X3 = col_character(),
  X4 = col_character(),
  X5 = col_character(),
  X6 = col_character()
)

52 parsing failures.
row col  expected    actual                           file
  2  -- 6 columns 3 columns 'data/malas_palabras_wiki.txt'
  3  -- 6 columns 2 columns 'data/malas_palabras_wiki.txt'
  4  -- 6 columns 3 columns 'data/malas_palabras_wiki.txt'
  5  -- 6 columns 4 columns 'data/malas_palabras_wiki.txt'
  6  -- 6 columns 2 columns 'data/malas_palabras_wiki.txt'
... ... ......... ......... ..............................
See problems(...) for more details.
malas_palabras_4 <- read_csv("data/palabras_profanas_es.txt", 
                             col_names = FALSE)

-- Column specification ---------------------------------------------------------------------------
cols(
  X1 = col_character()
)
malas_palabras <- rbind(malas_palabras_1, malas_palabras_2,
                        malas_palabras_3, malas_palabras_4)


#Hacer unique para eliminar las repetidas

malas_palabras$limpias = text_cleaning(malas_palabras$X1, language="es")

Inglés

#Genero lista de malas palabras
racist_words <- unique(tolower(lexicon::profanity_racist))

biglou <- read.csv("https://www.cs.cmu.edu/~biglou/resources/bad-words.txt", header=FALSE, col.names = c("words"))

MATRIZ TERMINO DOCUMENTO

corpus_esp
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 129

JOIN CATEGORICAS Y LYRICS

Inglés

Español

df_ly_feat_esp <- cbind(spa_lyrics[, 1:19], df_tm_esp)

nrow(spa_lyrics)
[1] 129
nrow(df_ly_feat_esp)
[1] 129
na.omit(df_ly_feat_esp)

mice::md.pattern(df_ly_feat_esp[, 153:ncol(df_ly_feat_esp)], rotate.names = T)
 /\     /\
{  `---'  }
{  O   O  }
==>  V <==  No need for mice. This data set is completely observed.
 \  \|/  /
  `-----'

    uah uoh vamo vas ven veo verte vida viene vuelvo woh work wuh yah yeah yeh you  
129   1   1    1   1   1   1     1    1     1      1   1    1   1   1    1   1   1 0
      0   0    0   0   0   0     0    0     0      0   0    0   0   0    0   0   0 0

df_ly_feat_ok_esp <- df_ly_feat_esp[, filter]

df_ly_feat_ok_esp$id = 1:nrow(df_ly_feat_ok_esp)

df_melt_esp <- reshape2::melt(data = df_ly_feat_ok_esp[,4:ncol(df_ly_feat_ok_esp)], id.vars = c("id"))  %>%
  arrange(id)
attributes are not identical across measure variables; they will be dropped
df_melt_esp <- df_melt_esp[df_melt_esp$value != 0,]

df_melt_esp_txt <- df_melt_esp[df_melt_esp$value == 1,]
df_melt_esp_cat <- df_melt_esp[df_melt_esp$value != 1,]

df_melt_esp_cat$variable =  paste0(df_melt_esp_cat$variable, "=", as.character(df_melt_esp_cat$value))


#denomino a los términos profanos
df_melt_esp_txt <- df_melt_esp_txt %>% 
  mutate(variable = case_when(as.character(variable) %in% malas_palabras$limpias ~
                                paste0("PROFANE_", as.character(variable)),
                              T ~ paste0("TERM_", as.character(variable))
                              )  
         )


df_melt_txt_to_ruls_esp <- rbind(df_melt_esp_txt, df_melt_esp_cat)

df_melt_txt_to_ruls_esp <- na.omit(df_melt_txt_to_ruls_esp[,-c(3)])
names(df_melt_txt_to_ruls_esp ) = c("TID", "item")

REGLAS

Ingles

reglas <- apriori(lyrics_trans, parameter = list(support=0.01,
                    confidence = 0.1, target  = "rules"  ))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 8 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[206 item(s), 852 transaction(s)] done [0.00s].
sorting and recoding items ... [203 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 6 7
Mining stopped (time limit reached). Only patterns up to a length of 7 returned!
 done [7.40s].
writing ... 

Inglés

Carga de Igal (UNIFICAR)

df_lyrics_unicas <- df_lyrics %>% distinct(artist_name, track_name, lyrics)
nrow(df_lyrics_unicas)

df_chart_w_lyrics <- merge(join_audio_charts, df_lyrics_unicas, by.x = c("artist_name","track_name"), by.y= c("artist_name","track_name"), all.x=TRUE, all.y = FALSE)

df_chart_w_lyrics <- df_chart_w_lyrics[!is.na(df_chart_w_lyrics$lyrics),]

Explicit

contar malas palabras (Parte de Igal: UNIFICAR)


bad_words <- c()
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_zac_anger)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_alvarez)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_arr_bad)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_racist)))
bad_words <- append(bad_words, unique(tolower(lexicon::profanity_banned)))

bad_words <- unique(bad_words)


contar_bad_words <- function(x){
  x <- profanity(x,profanity_list = bad_words)
  q <- sum(x$profanity_count)
  return (q)
  }
df_chart_w_lyrics$cant_bad_words <- sapply(df_chart_w_lyrics[,"lyrics"], contar_bad_words)


df_chart_w_lyrics_only_explicit <- df_chart_w_lyrics[df_chart_w_lyrics$explicit==TRUE & df_chart_w_lyrics$cant_bad_words > 0, ]

hist(df_chart_w_lyrics_only_explicit$cant_bad_words)


#creo vars categóricas
df_chart_w_lyrics_only_explicit$nivel_puteada <- cut(df_chart_w_lyrics_only_explicit$cant_bad_words, breaks = c(0,10,20,50,Inf), labels=c("bajo","poco","alto","muy_alto"))

df_chart_w_lyrics_only_explicit$nivel_ranking <- cut(df_chart_w_lyrics_only_explicit$position_avg, breaks = c(1,100,Inf), labels=c("1a100","100a200"))

df_chart_w_lyrics_only_explicit$nivel_popularidad <- cut(sqrt(df_chart_w_lyrics_only_explicit$cant_bad_words), breaks = c(0,10,20,50,Inf), labels=c("bajo","poco","alto","muy_alto"))

transactions <- as(as.data.frame(apply(df_chart_w_lyrics_only_explicit, 2, as.factor)), "transactions")
rules = apriori(transactions, parameter=list(target="rules", confidence=0.25, support=0.1))
rules.sub <- subset(rules, subset = lhs %pin% "nivel_puteada" & rhs %pin% "nivel_ranking")
inspect(head(sort(rules.sub, by = "lift", decreasing = TRUE),10))

# discretizacion continuas y seleccion de variables
# identificar palabras explít
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIExJQlJFUklBUw0KYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzcWxkZikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoc2VudGltZW50cikNCmxpYnJhcnkoYXJ1bGVzKQ0KbGlicmFyeShzdHJpbmdpKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkodGF1KQ0KYGBgDQoNCiMgQ0FSR0EgREUgREFUT1MgIA0KYGBge3J9DQpkZl9hcnRpc3QgPC0gcmVhZC5jc3YoImRhdGEvZGZfYXJ0aXN0X3Npbl9kdXBsaWNhZG9zLmNzdiIpDQogZGZfY2hhcnRzX3JhdyA8LSByZWFkLmNzdigiZGF0YS9kZl9jaGFydHNfc2luX2R1cGxpY2Fkb3MuY3N2IikNCmRmX2F1ZGlvX2ZlYXR1cmVzX3JhdyA8LSByZWFkLmNzdigiZGF0YS9hdWRpb19mZWF0dXJlc19wbGFub19zaW5fZHVwbGljYWRvcy5jc3YiKQ0KZGZfbHlyaWNzIDwtIHJlYWQuY3N2KCJkYXRhL2RmX2x5cmljcy5jc3YiKQ0KIA0KYGBgDQoNCg0KIyMgQ29ycmVjY2nDs24gZHVwbGljYWRvcw0KYGBge3J9DQojIERGIGxpc3RvIHBhcmEgZWwgam9pbiBjb24gY2hyYXRzDQpkZl9hdWRpb19mZWF0dXJlcyA8LSBkZl9hdWRpb19mZWF0dXJlc19yYXcgJT4lIA0KICBncm91cF9ieSh0cmFja19uYW1lLCBleHRlcm5hbF91cmxzX3Nwb3RpZnkpICU+JSANCiAgbXV0YXRlKGFydGlzdF9hbGwgPSBwYXN0ZShhcnRpc3RfbmFtZSwgY29sbGFwc2UgPSAiLHwsIikpICU+JQ0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUoYXJ0aXN0X2tleSA9IHN1YigiLHwsLioiLCAiIiwgYXJ0aXN0X2FsbCkpICU+JSANCiAgZHBseXI6OnNlbGVjdChhcnRpc3RfbmFtZSwgYXJ0aXN0X2FsbCwgYXJ0aXN0X2tleSwgZXZlcnl0aGluZyguKSkgJT4lIA0KICBkaXN0aW5jdChhcnRpc3Rfa2V5LCBleHRlcm5hbF91cmxzX3Nwb3RpZnksIC5rZWVwX2FsbCA9IFQpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQpgYGANCg0KIyMgQ3JlYWNpb24gYGNhbnRfbWFya2V0c2ANCmBgYHtyfQ0KY29udGFyX21hcmtldCA8LSBmdW5jdGlvbih4KXsNCnEgPC0gbGVuZ3RoKHVubGlzdChzdHJzcGxpdCh4LCBzcGxpdCA9ICIsIikpKQ0KcmV0dXJuIChxKQ0KICB9DQpkZl9hdWRpb19mZWF0dXJlcyRjYW50X21hcmtldHMgPC0gc2FwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzWywibWFya2V0c19jb25jYXQiXSwgY29udGFyX21hcmtldCkNCmBgYA0KDQoNCiMjIFZlY3RvcmVzIGRlIGZlYXR1cmVzDQpgYGB7cn0NCiNmZWF0dXJlcyB2YXIgY29udGludW9zDQpmZWF0dXJlc19jb250aW51YXMgPC0gYygnYWNvdXN0aWNuZXNzJywgJ2RhbmNlYWJpbGl0eScsICdkdXJhdGlvbl9tcycsICdlbmVyZ3knLCAnaW5zdHJ1bWVudGFsbmVzcycsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICAgJ3RlbXBvJywgJ3ZhbGVuY2UnLCAnY2FudF9tYXJrZXRzJykNCg0KI2ZlYXR1cmVzIHZhcl8gY2F0ZWfDs3JpY2FzDQpmZWF0dXJlc19jYXRlZ29yaWNhcyA8LSBjKCdleHBsaWNpdCcsICdrZXlfbmFtZScsICdtb2RlX25hbWUnLCAia2V5X21vZGUiKQ0KDQpgYGANCg0KDQojIyBJbXB1dGFjaW9uIGRlIGxvdWRuZXNzDQoNCg0KYGBge3J9DQpmaXQgPC0gbG0obG91ZG5lc3N+ZW5lcmd5K2Fjb3VzdGljbmVzcywgZGF0YT1kZl9hdWRpb19mZWF0dXJlcykNCg0KbW9kZWxvIDwtIGZpdCRjb2VmZmljaWVudHMNCg0KZGZfYXVkaW9fZmVhdHVyZXMkbG91ZG5lc3NfcmVnX2ltcCA8LSBkZl9hdWRpb19mZWF0dXJlcyRsb3VkbmVzcw0KDQpYIDwtIGRmX2F1ZGlvX2ZlYXR1cmVzW2RmX2F1ZGlvX2ZlYXR1cmVzJGxvdWRuZXNzPjAsIGMoJ2VuZXJneScsICJhY291c3RpY25lc3MiKV0NCg0KZGZfYXVkaW9fZmVhdHVyZXMkbG91ZG5lc3NfcmVnX2ltcFtkZl9hdWRpb19mZWF0dXJlcyRsb3VkbmVzcz4wXSA8LSBtb2RlbG9bMV0rbW9kZWxvWzJdKlhbLDFdK21vZGVsb1szXSpYWywyXQ0KDQpzdW1tYXJ5KGRmX2F1ZGlvX2ZlYXR1cmVzWyxjKCJsb3VkbmVzcyIsICJsb3VkbmVzc19yZWdfaW1wIildKQ0KDQpzdW1tYXJ5KGZpdCkNCg0KI2dyYWZpY29zIGNvbiBsb3VkbmVzcyBjb24gaW1wdXRhY2lvbg0KcGFyKG1mcm93ID0gYygyLDEpKSANCmhpc3QoZGZfYXVkaW9fZmVhdHVyZXNbLCdsb3VkbmVzc19yZWdfaW1wJ10sIG1haW49J2xvdWRuZXNzJywgeGxhYj0iIikNCiNoaXN0KHNxcnQoZGZfYXVkaW9fZmVhdHVyZXNbLCdsb3VkbmVzc19yZWdfaW1wJ10pLCBtYWluPSAnbG91ZG5lc3Nfc3FydCcsIHhsYWI9IiIpDQpib3hwbG90KGRmX2F1ZGlvX2ZlYXR1cmVzWywnbG91ZG5lc3NfcmVnX2ltcCddLCBob3Jpem9udGFsID0gVCkNCiNib3hwbG90KHNxcnQoZGZfYXVkaW9fZmVhdHVyZXNbLCdsb3VkbmVzc19yZWdfaW1wJ10pLCBob3Jpem9udGFsID0gVCkNCg0KDQoNCmBgYA0KDQoNCg0KIyBDSEFSVFM6IGFncmVnYWNpw7NuIGRlIGZlYXR1cmVzDQpgYGB7cn0NCiNtZXRyaWNhIGRlIHBvcHVsYXJpZGFkDQpkZl9jaGFydHMgPC0gZGZfY2hhcnRzX3JhdyAlPiUgDQogIGdyb3VwX2J5KEFydGlzdCwgVHJhY2tfTmFtZSkgJT4lDQogIGRwbHlyOjogc3VtbWFyaXNlKHNlbWFuYXNfc3VtID0gYXMuZG91YmxlKG4oKSksDQogICAgICAgICAgICBzdHJlYW1zX3N1bSA9IChzdW0oU3RyZWFtcywgbmEucm0gPSBUKS8xMF42ICksDQogICAgICAgICAgICBzdHJlYW1zX21pbiA9IChtaW4oU3RyZWFtcykvMTBeNiApLA0KICAgICAgICAgICAgc3RyZWFtc19tYXggPSAobWF4KFN0cmVhbXMpLzEwXjYgKSwNCiAgICAgICAgICAgIHN0cmVhbXNfYXZnID0gKG1lYW4oU3RyZWFtcykvMTBeNiksDQogICAgICAgICAgICBwb3NpdGlvbl9hdmcgPSBtZWFuKFBvc2l0aW9uLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgcG9zaXRpb25fbWVkaWFuID0gbWVkaWFuKFBvc2l0aW9uLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgcG9zaXRpb25fbWluID0gbWluKFBvc2l0aW9uKSwgDQogICAgICAgICAgICBwb3NpdGlvbl9tYXggPSBtYXgoUG9zaXRpb24pKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIG11dGF0ZShwb3B1bGFyaWRhZCA9IGFzLm51bWVyaWMoc3RyZWFtc19zdW0qc2VtYW5hc19zdW0vcG9zaXRpb25fYXZnKSApDQoNCmxpYnJhcnkocmVzaGFwZTIpDQpnZ3Bsb3QobWVsdChkZl9jaGFydHNbLDM6bmNvbChkZl9jaGFydHMpXSksIGFlcyh2YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbSgpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSAsIHNjYWxlcyA9ICJmcmVlIikNCg0KYGBgDQoNCg0KDQojIFJJR1RIIEpPSU4gYGF1ZGlvX2ZlYXR1cmVzYCBZIGBjaGFydHNgDQpgYGB7cn0NCiNBcm1hbW9zIHVuIGpvaW4gcGFyYSB0ZW5lciB1bmEgdGFibGEgZGUgY2hhcnRzIGNvbiBsYXMgY2FyYWN0ZXJpc3RpY2FzIGRlIGxhcyBjYW5jaW9uZXMNCiMgZGViZXJpYW4gcXVlZGFyIDIyOTkzIGZpbGFzIGNvbXBsZXRhcw0Kam9pbl9hdWRpb19jaGFydHMgPC0gZGZfYXVkaW9fZmVhdHVyZXMgJT4lIA0KICBzZWxlY3QoImFydGlzdF9uYW1lIiwiYXJ0aXN0X2FsbCIsImFydGlzdF9rZXkiLA0KICAgICAgICAgInRyYWNrX25hbWUiLCAiZXh0ZXJuYWxfdXJsc19zcG90aWZ5IiwgImFsYnVtX25hbWUiLCAiYWxidW1fcmVsZWFzZV95ZWFyIiwNCiAgICAgICAgIGFsbF9vZihmZWF0dXJlc19jb250aW51YXMpLCBhbGxfb2YoZmVhdHVyZXNfY2F0ZWdvcmljYXMpKSAlPiUgDQogIHJpZ2h0X2pvaW4oIGRmX2NoYXJ0cywjICU+JQ0KICAgICAgICAgICAgICAgYnkgPSBjKA0KICAgICAgICAgICAgICAgICAidHJhY2tfbmFtZSIgPSAiVHJhY2tfTmFtZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICJhcnRpc3Rfa2V5IiA9IkFydGlzdCIpKQ0KDQojSEFZIENIQVJUUyBRVUUgTk8gVElFTkVOIEZFQVRVUkVTLiBIQVkgUVVFIFRFTkVSTE8gRU4gQ1VFTlRBIFBBUkEgRUwgQU7DgUxJU0lTDQpsaWJyYXJ5KG1pY2UpDQptZC5wYXR0ZXJuKGpvaW5fYXVkaW9fY2hhcnRzLCByb3RhdGUubmFtZXMgPSBUUlVFKQ0KDQpgYGANCg0KDQoNCiMgSElTVE9HUkFNQVMgWSBCQVJQTE9UUyBERSBWQVJJQUJMRVMNCmBgYHtyfQ0KDQojI2hpc3RvZ3JhbWEgZGUgbGFzIHZhcmlhYmxlcyBjb250aW51YXMgZGUgYXVkaW9fZmVhdHVyZXMNCg0KZm9yIChpIGluIGZlYXR1cmVzX2NvbnRpbnVhcyl7DQoNCiAgaGlzdChkZl9hdWRpb19mZWF0dXJlc1ssaV0sIG1haW4gPSBwYXN0ZSgiSGlzdG9ncmFtYSBkZSIsIGksICIoYWxsIGRhdGEpIiksIHhsYWIgPSBpKQ0KICBhYmxpbmUodiA9IG1lYW4oZGZfYXVkaW9fZmVhdHVyZXNbLGldLCBuYS5ybSA9IFRSVUUpICwgY29sPSJyZWQiKQ0KICBhYmxpbmUodiA9IG1lZGlhbihkZl9hdWRpb19mZWF0dXJlc1ssaV0sIG5hLnJtID0gVFJVRSkgLCBjb2w9ImJsdWUiKQ0KICBsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiTWVkaWEiLCAiTWVkaWFuYSIpLCBjb2w9YygicmVkIiwgImJsdWUiKSwgbHR5ID0xKQ0KDQp9DQoNCiNkaXZpZG8gbG9zIGZlYXR1cmVzIHBvciBzdSBkaXN0cmlidWNpw7NuDQpmZWF0dXJlc19jb250aW51YXNfbWVkaWEgPC0gYygnZGFuY2VhYmlsaXR5JywgJ3RlbXBvJywgJ3ZhbGVuY2UnKQ0KDQpmZWF0dXJlc19jb250aW51YXNfbWVkaWFuYSA8LSBjKCdhY291c3RpY25lc3MnLCAnZHVyYXRpb25fbXMnLCAnZW5lcmd5JywgJ2luc3RydW1lbnRhbG5lc3MnLCAnbGl2ZW5lc3MnLCAnbG91ZG5lc3MnLCAnc3BlZWNoaW5lc3MnLCAnY2FudF9tYXJrZXRzJykNCg0KDQojI2hpc3RvZ3JhbWEgZGUgbGFzIHZhcmlhYmxlcyBjb250aW51YXMgZGUgY2hhcnRzDQpmb3IgKGkgaW4gYyhmZWF0dXJlc19jb250aW51YXMpKXsNCg0KICBoaXN0KGpvaW5fYXVkaW9fY2hhcnRzWyxpXSwgbWFpbiA9IHBhc3RlKCJIaXN0b2dyYW1hIGRlIiwgaSwgICIoY2hhcnRzKSIpLCB4bGFiID0gaSkNCiAgYWJsaW5lKHYgPSBtZWFuKGpvaW5fYXVkaW9fY2hhcnRzWyxpXSwgbmEucm0gPSBUUlVFKSAsIGNvbD0icmVkIikNCiAgYWJsaW5lKHYgPSBtZWRpYW4oam9pbl9hdWRpb19jaGFydHNbLGldLCBuYS5ybSA9IFRSVUUpICwgY29sPSJibHVlIikNCg0KfQ0KDQojZGl2aWRvIGZlYXR1cmVzIGRlIGNoYXJ0cyBzZWfDum4gc3UgZGlzdHJpYnVjacOzbg0KYXVkaW9fY2hhcnRzX2NvbnRpbnVhc19tZWRpYSA8LSBjKCdkdXJhdGlvbl9tcycsICd2YWxlbmNlJykNCg0KYXVkaW9fY2hhcnRzX2NvbnRpbnVhc19tZWRpYW5hIDwtIGMoJ2RhbmNlYWJpbGl0eScsICdhY291c3RpY25lc3MnLCAndGVtcG8nLCAnZW5lcmd5JywgJ2luc3RydW1lbnRhbG5lc3MnLCAnbGl2ZW5lc3MnLCAnbG91ZG5lc3MnLCAnc3BlZWNoaW5lc3MnLCAnY2FudF9tYXJrZXRzJywgIlN0cmVhbXMiKQ0KDQoNCiMjbWVkaWRhcyByZXN1bWVuIHkgYmFycGxvdHMgZGUgbGFzIHZhcmlhYmxlcyBjYXRlZ29yaWNhcyBhdWRpb19mZWF0dXJlcw0KZm9yKGkgaW4gZmVhdHVyZXNfY2F0ZWdvcmljYXMpew0KDQogIGJhcnBsb3Qoc29ydCh0YWJsZShkZl9hdWRpb19mZWF0dXJlc1ssaV0pLGRlY3JlYXNpbmcgPSBUKSwgbGFzPTIsIA0KICAgICAgICAgIG1haW4gPSBwYXN0ZSgiQmFycGxvdCBkZSIsIGksICIoYWxsIGRhdGEpIikpDQogICMgcGllKHRhYmxlKGRmX2ZlYXR1cmVzX2NhdGVnb3JpY29zWyxpXSkpDQp9DQoNCg0KDQojI21lZGlkYXMgcmVzdW1lbiB5IGJhcnBsb3RzIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWdvcmljYXMgam9pbl9hdWRpb19jaGFydHMNCg0KZm9yKGkgaW4gZmVhdHVyZXNfY2F0ZWdvcmljYXMpew0KICBiYXJwbG90KHRhYmxlKGpvaW5fYXVkaW9fY2hhcnRzWyxpXSksIGxhcz0yLA0KICAgICAgICAgIG1haW4gPSBwYXN0ZSgiQmFycGxvdCBkZSIsIGksICIoY2hhcnRzKSIpDQogICAgICAgICAgKQ0KICAjIHBpZSh0YWJsZShkZl9mZWF0dXJlc19jYXRlZ29yaWNvc1ssaV0pKQ0KfQ0KDQpoaXN0KGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MpDQpgYGANCg0KDQoNCiMgU0VTR08gREUgVkFSSUFCTEVTIA0KDQoNCiMjIEJveHBsb3RzIFZhcmlhYmxlcyBOdW3DqXJpY2FzIHNpbiBmaWx0cmFyIG91dGxpZXJzDQpgYGB7cn0NCnBhcihtZnJvdz1jKDQsMykpDQpmb3IgKGZlYXR1cmUgaW4gZmVhdHVyZXNfY29udGludWFzKXsNCiAgYm94cGxvdChkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIGxhcz0yLCBob3Jpem9udGFsPVQsIG1haW49ZmVhdHVyZSkNCn0NCmBgYA0KDQpDb24gZXhjZXBjacOzbiBkZSB2YWxlbmNlIGVsIHJlc3RvIGRlIGxhcyBmZWF0dXJlcyBwb3Nlw61hbiBjaWVydG8gc2VzZ28uIFNlIGRlY2lkacOzIHRyYW5zZm9ybWFyIGxhcyB2YXJpYWJsZXMgcXVlIG1heW9yIHNlc2dvIHBvc2XDrWFuOiBkdXJhdGlvbl9tcywgaW5zdHJ1bWVudGFsbmVzcywgbGl2ZW5lc3MsIHNwZWVjaGluZXNzIGNvbW8gbcOpdG9kbyBkZSBjb3JyZWdpciBsYSBkaXN0cmlidWNpw7NuIHkgYWNoaWNhciBsYSBjYW50aWRhZCBkZSBvdXRsaWVycy4gTGEgdmFyaWFibGUgbG91ZG5lc3NfcmVnX2ltcCBubyBmdWUgbW9kaWZpY2FkYSBkZWJpZG8gYSBxdWUgYWwgc2VyIG5lZ2F0aXZhIA0KDQojIyBUcmFuc2Zvcm1hY2lvbmVzDQoNCiMjIyBUcmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhDQpgYGB7cn0NCiMgImRhbmNlYWJpbGl0eSx0ZW1wbyx2YWxlbmNlLGFjb3VzdGljbmVzcyxkdXJhdGlvbl9tcyxlbmVyZ3ksaW5zdHJ1bWVudGFsbmVzcyxsaXZlbmVzcyxzcGVlY2hpbmVzcyxjYW50X21hcmtldHMiDQoNCiNzZXNnb3MgZCBsYXMgdmFyaWFibGVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpzb3J0KGFwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzWyxmZWF0dXJlc19jb250aW51YXNdLCBNQVJHSU4gPSAyLCBmdW5jdGlvbih4KXsgKDMqIChtZWFuKHgsbmEucm0gPSBUKS1tZWRpYW4oeCwgbmEucm0gPSBUKSkpL3NkKHgsIG5hLnJtID0gVCl9ICkpDQoNCnZhcmlhYmxlc19zZXNnbyA8LSB1bmxpc3Qoc3Ryc3BsaXQoImFjb3VzdGljbmVzcyxkdXJhdGlvbl9tcyxpbnN0cnVtZW50YWxuZXNzLGxpdmVuZXNzLHNwZWVjaGluZXNzLGNhbnRfbWFya2V0cyxlbmVyZ3kiLCAiLCIpKQ0KDQpkZl9zZXNnYWRhcyA8LSBkZl9hdWRpb19mZWF0dXJlc1ssdmFyaWFibGVzX3Nlc2dvXQ0KDQpsb2dhcml0bW9fYWp1c3RhZG8gPSBmdW5jdGlvbih4LGRlbHRhKXsNCiAgaWYgKHg9PTAuMCl7DQogICAgcmV0dXJuKGxvZygwLjAwK2RlbHRhLCBiYXNlID0gMTApKQ0KICB9ZWxzZXsNCiAgICByZXR1cm4obG9nKHgsIGJhc2UgPSAxMCkpDQogIH0NCn0NCmRlbHRhIDwtIDEwXigtNikNCg0KZGZfc2VzZ2FkYXNfbG9nX2FkanVzdCA8LSBkYXRhLmZyYW1lKGFwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzWyx2YXJpYWJsZXNfc2VzZ29dLCBNQVJHSU4gPSBjKDEsMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIGxvZ2FyaXRtb19hanVzdGFkbyh4LGRlbHRhKSkpDQoNCmdncGxvdChyZXNoYXBlOjptZWx0KGRmX3Nlc2dhZGFzKSwgYWVzKHZhbHVlKSkrDQogIGdlb21faGlzdG9ncmFtKCkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlKQ0KDQpnZ3Bsb3QocmVzaGFwZTo6bWVsdChkZl9zZXNnYWRhc19sb2dfYWRqdXN0KSwgYWVzKHZhbHVlKSkrDQogIGdlb21faGlzdG9ncmFtKCkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlKQ0KDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgbmFtZXMoZGZfc2VzZ2FkYXNfbG9nX2FkanVzdCkgPC0gcGFzdGUobmFtZXMoZGZfc2VzZ2FkYXMpLCAiX2xvZyIsIHNlcD0iIikNCiMgbmFtZXMoZGZfc2VzZ2FkYXNfbG9nX2FkanVzdCkgPC0gbmFtZXMoZGZfc2VzZ2FkYXMpDQojIGRmX2RhdG9zIDwtIGNiaW5kKGRmX3Nlc2dhZGFzLCBkZl9zZXNnYWRhc19sb2dfYWRqdXN0KQ0KDQphIDwtIGRmX3Nlc2dhZGFzDQpiIDwtIGRmX3Nlc2dhZGFzX2xvZ19hZGp1c3QNCm5hbWVzKGIpIDwtIHBhc3RlKG5hbWVzKGRmX3Nlc2dhZGFzKSwgIl9sb2ciLCBzZXA9IiIpDQptZXJnZWQgPC0gY2JpbmQoYSxiKQ0KDQptZXJnZWQgPC0gbWVyZ2VkWywgb3JkZXIobmFtZXMobWVyZ2VkKSldDQoNCnJvdW5kKCgNCiAgYXBwbHkoDQogIG1lcmdlZCwgTUFSR0lOID0gMiwgZnVuY3Rpb24oeCl7ICgzKiAobWVhbih4LG5hLnJtID0gVCktbWVkaWFuKHgsIG5hLnJtID0gVCkpKS9zZCh4LCBuYS5ybSA9IFQpfQ0KICAgICAgICApDQogICAgICAgICAgKSwyKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiNoaXN0b2dyYW1hcyBkZSB2YmxlcyB0cmFuc2Zvcm1hZGFzIGNvbiBsb2dhcml0bW8NCiMgdHJhbnNmb3JtYWNpb24gPC0gYygnaW5zdHJ1bWVudGFsbmVzcycsJ2xvdWRuZXNzJywnbGl2ZW5lc3MnLCdzcGVlY2hpbmVzcycsICdkdXJhdGlvbl9tcycpDQoNCnBhcihtZnJvdz1jKDMsNSkpDQpmb3IgKGZlYXR1cmUgaW4gdmFyaWFibGVzX3Nlc2dvKXsNCiAgaGlzdChkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIG1haW49ZmVhdHVyZSkNCn0NCg0KZm9yIChmZWF0dXJlIGluIHZhcmlhYmxlc19zZXNnbyl7DQogIGhpc3QodW5saXN0KGxhcHBseShkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIGZ1bmN0aW9uKHgpIGxvZ2FyaXRtb19hanVzdGFkbyh4LGRlbHRhKSkpLCBtYWluPXBhc3RlKGZlYXR1cmUsImxvZyIsIHNlcD0iXyIpKQ0KfQ0KYGBgDQojIyMgVHJhbnNmb3JtYWNpb24gaW52ZXJzYSByYWl6IGN1YWRyYWRhDQoNCmBgYHtyfQ0KDQppbnZfc3FydF9hanVzdGFkYSA9IGZ1bmN0aW9uKHgsIGRlbHRhKXsNCiAgaWYgKHg9PTAuMCl7DQogICAgcmV0dXJuKDEvc3FydCh4KzAuMDAwMDAxKSkNCiAgfWVsc2V7DQogICAgcmV0dXJuKDEvc3FydCh4KSkNCiAgfQ0KfQ0KZGVsdGEgPC0gc3FydCgxMF4oLTYpKQ0KDQoNCnBhcihtZnJvdz1jKDMsNSkpDQpmb3IgKGZlYXR1cmUgaW4gdmFyaWFibGVzX3Nlc2dvKXsNCiAgaGlzdChkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIG1haW49ZmVhdHVyZSkNCn0NCg0KZm9yIChmZWF0dXJlIGluIHZhcmlhYmxlc19zZXNnbyl7DQogIGhpc3QodW5saXN0KGxhcHBseShkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZV0sIGZ1bmN0aW9uKHgpIGludl9zcXJ0X2FqdXN0YWRhKHgsZGVsdGEpKSksIG1haW49cGFzdGUoZmVhdHVyZSwiaW52X3NxdCIsIHNlcD0iXyIpKQ0KfQ0KDQpkZl9zZXNnYWRhc19pbnZfc3FydCA8LSBkYXRhLmZyYW1lKGFwcGx5KGRmX2F1ZGlvX2ZlYXR1cmVzWyx2YXJpYWJsZXNfc2VzZ29dLCBNQVJHSU4gPSBjKDEsMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIGludl9zcXJ0X2FqdXN0YWRhKHgsZGVsdGEpKSkNCg0KYGBgDQoNCg0KYGBge3J9DQojbnVldm9zIHNlc2dvcyBjb24gaW52ZXJzYSByYWl6IGN1YWRyYWRhDQphIDwtIGRmX3Nlc2dhZGFzDQpiIDwtIGRmX3Nlc2dhZGFzX2ludl9zcXJ0DQpuYW1lcyhiKSA8LSBwYXN0ZShuYW1lcyhkZl9zZXNnYWRhcyksICJfaW52c3FydCIsIHNlcD0iIikNCm1lcmdlZCA8LSBjYmluZChhLGIpDQoNCm1lcmdlZCA8LSBtZXJnZWRbLCBvcmRlcihuYW1lcyhtZXJnZWQpKV0NCg0Kcm91bmQoKA0KICBhcHBseSgNCiAgbWVyZ2VkLCBNQVJHSU4gPSAyLCBmdW5jdGlvbih4KXsgKDMqIChtZWFuKHgsbmEucm0gPSBUKS1tZWRpYW4oeCwgbmEucm0gPSBUKSkpL3NkKHgsIG5hLnJtID0gVCl9DQogICAgICAgICkNCiAgICAgICAgICApLDIpDQoNCg0KYiA8LSBkZl9zZXNnYWRhc19pbnZfc3FydFssYygiY2FudF9tYXJrZXRzIiwgImVuZXJneSIsICJpbnN0cnVtZW50YWxuZXNzIiwgImxpdmVuZXNzIiAsICJzcGVlY2hpbmVzcyIpXQ0KbmFtZXMoYikgPC0gcGFzdGUobmFtZXMoYiksICJfaW52c3FydCIsIHNlcD0iIikNCg0KcGFyKG1mcm93PWMoMSwyKSkNCmdncGxvdChkYXRhPXJlc2hhcGU6Om1lbHQoYiksIGFlcyh2YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNSkrZmFjZXRfd3JhcCh+dmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikNCg0KZ2dwbG90KGRhdGE9cmVzaGFwZTo6bWVsdChhKSwgYWVzKHZhbHVlKSkrDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA1KStmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKQ0KDQpgYGANCg0KDQojIyBTZXNnbyBERiBjaGFydHMNCmBgYHtyfQ0KZGZfc2VzX2NoYXJ0IDwtIGRmX2NoYXJ0cyAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHNlbGVjdCggIlRyYWNrX05hbWUiLCAiQXJ0aXN0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICJzdHJlYW1zX2F2ZyIsICJwb3NpdGlvbl9hdmciLA0KICAgICAgICAgICAgICAgICAgICAgICAic2VtYW5hc19zdW0iLCAicG9wdWxhcmlkYWQiKQ0KDQoNCg0KZGZfc2VzX2NoYXJ0X2xvZyA8LSBkYXRhLmZyYW1lKGFwcGx5KGRmX3Nlc19jaGFydFssMzo2XSwgTUFSR0lOID0gYygxLDIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBsb2dhcml0bW9fYWp1c3RhZG8oeCxkZWx0YSkpKQ0KDQpjYXQoIm9yaWdpbmFsZXNcbiIpDQojc2VzZ29zIGQgbGFzIHZhcmlhYmxlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KKGFwcGx5KGRmX3Nlc19jaGFydFssMzo2XSwgTUFSR0lOID0gMiwgZnVuY3Rpb24oeCl7ICgzKiAobWVhbih4LG5hLnJtID0gVCktbWVkaWFuKHgsIG5hLnJtID0gVCkpKS9zZCh4LCBuYS5ybSA9IFQpfSApKQ0KDQpjYXQoIlxudHJhbnNmb3JtYWRhc1xuIikNCihhcHBseShkZl9zZXNfY2hhcnRfbG9nLCBNQVJHSU4gPSAyLCBmdW5jdGlvbih4KXsgKDMqIChtZWFuKHgsbmEucm0gPSBUKS1tZWRpYW4oeCwgbmEucm0gPSBUKSkpL3NkKHgsIG5hLnJtID0gVCl9ICkpDQoNCg0KZ2dwbG90KHJlc2hhcGUyOjptZWx0KGRmX3Nlc19jaGFydFssMzo2XSksIGFlcyh2YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbSgpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hcyBvcmlnaW5hbGVzIikNCg0KZ2dwbG90KHJlc2hhcGU6Om1lbHQoZGZfc2VzX2NoYXJ0X2xvZyksIGFlcyh2YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbSgpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hcyB0cmFuc2Zvcm1hZG9zIikNCg0KDQpgYGANCg0KDQpgYGB7cn0NCmRmX3Nlc19jaGFydF9pbnZfc3FydCA8LSBkYXRhLmZyYW1lKGFwcGx5KGRmX3Nlc19jaGFydFssMzo2XSwgTUFSR0lOID0gYygxLDIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBpbnZfc3FydF9hanVzdGFkYSh4LGRlbHRhKSkpDQoNCmNhdCgiXG50cmFuc2Zvcm1hZGFzXG4iKQ0KKGFwcGx5KGRmX3Nlc19jaGFydF9pbnZfc3FydCwgTUFSR0lOID0gMiwgZnVuY3Rpb24oeCl7ICgzKiAobWVhbih4LG5hLnJtID0gVCktbWVkaWFuKHgsIG5hLnJtID0gVCkpKS9zZCh4LCBuYS5ybSA9IFQpfSApKQ0KDQoNCmNoYXJ0c19zaW5fc2VzZ28gPC0gY2JpbmQoZGZfc2VzX2NoYXJ0WywtYygzLDUpXSwgDQogICAgICJzZW1hbmFzX3N1bSIgPWRmX3Nlc19jaGFydF9sb2dbLGMoInNlbWFuYXNfc3VtIildLA0KICAgICAic3RyZWFtc19hdmciID0gZGZfc2VzX2NoYXJ0X2ludl9zcXJ0WyxjKCJzdHJlYW1zX2F2ZyIpXSkNCg0KDQpgYGANCg0KDQojIyBOb3JtYWxpemFjaW9uDQoNCiMjIyBBdWRpbyBmZWF0dXJlcw0KYGBge3J9DQojam9pbiBlbnRyZSB2YXJpYWJsZXMgdHJhbnNmb3JtYWRhcyB5IHJlc3RvIGZlYXR1cmVzDQphdWRpb19zaW5fc2VzZ28gPC0gZGZfYXVkaW9fZmVhdHVyZXMgJT4lIA0KICBzZWxlY3QoImFydGlzdF9uYW1lIiwgImFydGlzdF9rZXkiLA0KICAgICAgICAgInRyYWNrX25hbWUiLA0KICAgICAgICAgYWxsX29mKGZlYXR1cmVzX2NvbnRpbnVhcyksIGFsbF9vZihmZWF0dXJlc19jYXRlZ29yaWNhcykpICU+JQ0KICBzZWxlY3QoIXZhcmlhYmxlc19zZXNnbykgDQoNCg0KYXVkaW9fZnRfdG9fZGlzY3JldGl6ZSA8LSBjYmluZChhdWRpb19zaW5fc2VzZ28sIGRmX3Nlc2dhZGFzX2xvZ19hZGp1c3RbLGMoImFjb3VzdGljbmVzcyIsICJkdXJhdGlvbl9tcyIpXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmX3Nlc2dhZGFzX2ludl9zcXJ0WyxjKCJpbnN0cnVtZW50YWxuZXNzIiwgImNhbnRfbWFya2V0cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGl2ZW5lc3MiICwgInNwZWVjaGluZXNzIildKSAlPiUNCiAgICBzZWxlY3QoLWMoICJrZXlfbmFtZSIsICJleHBsaWNpdCIsICJtb2RlX25hbWUiICwia2V5X21vZGUiKSkgJT4lIA0KICAgZ3JvdXBfYnkoYXJ0aXN0X25hbWUsIGFydGlzdF9rZXksIHRyYWNrX25hbWUpICAlPiUgDQogIGRpc3RpbmN0KC5rZWVwX2FsbCA9IFQpDQogIA0KDQpgYGANCg0KDQpgYGB7cn0NCiNub3JtYWxpemFjaW9uIGRlIHRvZGFzIGxhcyB2Ymxlcw0Kc2NhbGVfdmJsZSA8LSBmdW5jdGlvbih4KXsNCiAgKHggLSBtZWFuKHgsIG5hLnJtID0gVCkpL3NkKHgsIG5hLnJtID0gVCkNCn0NCg0KYXVkaW9fZnRfdG9fZGlzY3JldGl6ZV9ub3JtIDwtIGF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemUgJT4lIA0KICAjIG11dGF0ZV9hbGwoc2NhbGUpDQogIG11dGF0ZV9hbGwoc2NhbGVfdmJsZSkjDQpgYGANCg0KDQpgYGB7cn0NCmdncGxvdChkYXRhPSBtZWx0KGF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemUsIGlkLnZhcnMgPSBjKCJhcnRpc3RfbmFtZSIsICJhcnRpc3Rfa2V5IiwgInRyYWNrX25hbWUiKSksIGFlcyh2YWx1ZSkpKw0KICAgICAgICAgZ2VvbV9oaXN0b2dyYW0oKSsNCiAgZmFjZXRfd3JhcCh+dmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikNCg0KZ2dwbG90KGRhdGE9IG1lbHQoYXVkaW9fZnRfdG9fZGlzY3JldGl6ZV9ub3JtLCBpZC52YXJzID0gYygiYXJ0aXN0X25hbWUiLCAiYXJ0aXN0X2tleSIsICJ0cmFja19uYW1lIikpLCBhZXModmFsdWUpKSsNCiAgICAgICAgIGdlb21faGlzdG9ncmFtKCkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpDQoNCmBgYA0KIyMjIENoYXJ0cw0KYGBge3J9DQpjaGFydHNfc2luX3Nlc2dvX25vcm0gPC0gY2hhcnRzX3Npbl9zZXNnbyAlPiUNCiAgcmVuYW1lKHRyYWNrX25hbWUgPSBUcmFja19OYW1lLCBhcnRpc3QgPSBBcnRpc3QpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgc2VsZWN0KC1jKHRyYWNrX25hbWUsIGFydGlzdCkpICU+JQ0KICBtdXRhdGVfYWxsKGZ1bmN0aW9uKHgpIHtzY2FsZV92YmxlKHgpfSkNCiAgIyBtdXRhdGVfYXQoLiwgLnZhcnMgPSB2YXJzKC1ncm91cF9jb2xzKCJUcmFja19OYW1lIiwgIkFydGlzdCIpKSwgZnVuY3Rpb24oeCkge3NjYWxlX3ZibGUoeCl9KQ0KDQpjaGFydHNfc2luX3Nlc2dvX25vcm0gPC0gY2JpbmQoY2hhcnRzX3Npbl9zZXNnb1ssIGMoIkFydGlzdCIsICJUcmFja19OYW1lIildLCBjaGFydHNfc2luX3Nlc2dvX25vcm0gKQ0KYGBgDQoNCg0KDQojIERpc2NyZXRpemFjacOzbg0KDQojIERpc2NyZXRpemFjacOzbg0KIyMgQXVkaW8gRmVhdHVyZXMNCg0KYGBge3J9DQoNCiNmdW5jaW9uZXMNCmN1dF9tZWRpYW4gPC0gZnVuY3Rpb24oeCl7DQogIGN1dCh4LCANCiAgICAgIGJyZWFrcyA9IGMoMCxtZWRpYW4oeCkvMiwNCiAgICAgICAgICAgICAgICAgbWVkaWFuKHgpLA0KICAgICAgICAgICAgICAgICBxdWFudGlsZSh4LHByb2JzID0gMC43NSksDQogICAgICAgICAgICAgICAgIEluZiksIA0KICAgICAgaW5jbHVkZS5sb3dlc3QgPSBULA0KICAgICAgbGFiZWxzPWMoIkJhamEiLCJNZWRpYSIsIkFsdGEiLCAiTXV5IGFsdGEiKSApDQogIA0KfQ0KDQpjdXRfYmluYXJ5IDwtIGZ1bmN0aW9uKHgpew0KICBjdXQoeCwNCiAgICAgIGJyZWFrcyA9IGMobWluKHgsbmEucm0gPSBUKSwNCiAgICAgICAgICAgICAgICAgbWVkaWFuKHgpLA0KICAgICAgICAgICAgICAgICBJbmYpLCANCiAgICAgIGluY2x1ZGUubG93ZXN0ID0gVCwNCiAgICAgIGxhYmVscz1jKCJCYWphIiwiQWx0YSIpKQ0KfQ0KDQpjdXRfY3VhbnRpbGUgPC0gZnVuY3Rpb24oeCl7DQogIGN1dCh4LCANCiAgICAgIGJyZWFrcyA9IHF1YW50aWxlKHgpLCANCiAgICAgIGluY2x1ZGUubG93ZXN0ID0gVCwNCiAgICAgIGxhYmVscz1jKCJCYWphIiwiTWVkaWEiLCJBbHRhIiwgIk11eSBhbHRhIikgKQ0KfQ0KDQoNCiNzZWxlY3QgZGF0YQ0KZGZfYXVkaW9fZnRfc2VsZWN0IDwtZGZfYXVkaW9fZmVhdHVyZXMgJT4lIA0KICBzZWxlY3QoYXJ0aXN0X25hbWUsIGFydGlzdF9rZXksIHRyYWNrX25hbWUsIGZlYXR1cmVzX2NvbnRpbnVhcywgLWxvdWRuZXNzLGxvdWRuZXNzID0gbG91ZG5lc3NfcmVnX2ltcCApICU+JQ0KICBncm91cF9ieSh0cmFja19uYW1lLCBhcnRpc3Rfa2V5LCBhcnRpc3RfbmFtZSkgJT4lDQogIHN1bW1hcmlzZV9hbGwoZnVuY3Rpb24oeCkgbWVhbih4LCBuYS5ybSA9IFQpKSAlPiUgDQogIGxlZnRfam9pbihkZl9hdWRpb19mZWF0dXJlcyAlPiUgDQogICAgICAgICAgICAgICBzZWxlY3QoYXJ0aXN0X25hbWUsIGFydGlzdF9rZXksIHRyYWNrX25hbWUsIGV4cGxpY2l0LCBtb2RlX25hbWUpICU+JSANCiAgICAgICAgICAgICAgIGRpc3RpbmN0KGFydGlzdF9uYW1lLCBhcnRpc3Rfa2V5LCB0cmFja19uYW1lLCAua2VlcF9hbGwgPSBUKSApDQogICAgICAgICAgICAgIA0KDQojY3JlYWNpb24gY29sdW1uYXMNCiNieSBtZWRpYW4NCmRmX2F1ZGlvX2Z0X3NlbGVjdCRhY291c3RpY25lc3NfY2F0IDwtIGN1dF9tZWRpYW4oZGZfYXVkaW9fZnRfc2VsZWN0JGFjb3VzdGljbmVzcykNCmRmX2F1ZGlvX2Z0X3NlbGVjdCRkdXJhdGlvbl9tc19jYXQgPC0gY3V0X21lZGlhbihkZl9hdWRpb19mdF9zZWxlY3QkZHVyYXRpb25fbXMpDQpkZl9hdWRpb19mdF9zZWxlY3QkbGl2ZW5lc3NfY2F0IDwtY3V0X21lZGlhbihkZl9hdWRpb19mdF9zZWxlY3QkbGl2ZW5lc3MpDQpkZl9hdWRpb19mdF9zZWxlY3Qkc3BlZWNoaW5lc3NfY2F0IDwtIGN1dF9tZWRpYW4oZGZfYXVkaW9fZnRfc2VsZWN0JHNwZWVjaGluZXNzKQ0KDQojYnkgYmluYXJ5DQpkZl9hdWRpb19mdF9zZWxlY3QkaW5zdHJ1bWVudGFsbmVzc19jYXQgPC0gY3V0X2JpbmFyeShkZl9hdWRpb19mdF9zZWxlY3QkaW5zdHJ1bWVudGFsbmVzcykNCmRmX2F1ZGlvX2Z0X3NlbGVjdCRsb3VkbmVzc19jYXQgPC0gY3V0X2JpbmFyeShkZl9hdWRpb19mdF9zZWxlY3QkbG91ZG5lc3MpDQoNCiNieSBjdW5hdGlsZQ0KZGZfYXVkaW9fZnRfc2VsZWN0JHRlbXBvX2NhdCA8LSBjdXRfY3VhbnRpbGUoZGZfYXVkaW9fZnRfc2VsZWN0JHRlbXBvKQ0KZGZfYXVkaW9fZnRfc2VsZWN0JHZhbGVuY2VfY2F0IDwtIGN1dF9jdWFudGlsZShkZl9hdWRpb19mdF9zZWxlY3QkdmFsZW5jZSkNCmRmX2F1ZGlvX2Z0X3NlbGVjdCRkYW5jZWFiaWxpdHlfY2F0IDwtIGN1dF9jdWFudGlsZShkZl9hdWRpb19mdF9zZWxlY3QkZGFuY2VhYmlsaXR5KQ0KDQojY2FudCBtYXJrZXRzDQpkZl9hdWRpb19mdF9zZWxlY3QkY2FudF9tYXJrZXRzX2NhdCA8LSBjdXQoZGZfYXVkaW9fZnRfc2VsZWN0JGNhbnRfbWFya2V0cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygwLCA3MCwgMTEwLCBJbmYpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJCYWphIiwiTWVkaWEiLCJBbHRhIikpDQoNCiN0cnVlIGNhdGVnb3JpZXMgdmJsZXMNCmRmX2F1ZGlvX2Z0X3NlbGVjdCRleHBsaWNpdF9jYXQgPC0gaWZlbHNlKGRmX2F1ZGlvX2Z0X3NlbGVjdCRleHBsaWNpdCA9PVRSVUUsICJTaSIsICJObyIpDQpkZl9hdWRpb19mdF9zZWxlY3QkbW9kZV9uYW1lX2NhdCA8LSBkZl9hdWRpb19mdF9zZWxlY3QkbW9kZV9uYW1lDQoNCiMgZmlsdHJvDQpkZl9hdWRpb19mdF9jYXQgPC0gZGZfYXVkaW9fZnRfc2VsZWN0ICU+JSANCiAgc2VsZWN0KCBhcnRpc3RfbmFtZSwgYXJ0aXN0X2tleSwgdHJhY2tfbmFtZSwgY29udGFpbnMoIl9jYXQiKSAgKQ0KDQoNCnggPC0gIGRmX2F1ZGlvX2Z0X2NhdCA8LSBkZl9hdWRpb19mdF9zZWxlY3QgJT4lIA0KICBzZWxlY3QoIGNvbnRhaW5zKCJfY2F0IikgICkNCg0KZm9yKGkgaW4gbmFtZXMoeCkgKXsNCiAgYmFycGxvdCh0YWJsZSh4WyxpXSksIGxhcz0yLA0KICAgICAgICAgIG1haW4gPSBwYXN0ZSgiQmFycGxvdCBkZSIsIGksICIoY2hhcnRzKSIpICkNCg0KICAgIyBjYXQodGFibGUoICB4WyxpXSApKQ0KICAgfQ0KDQoNCmBgYA0KDQoNCiMjIENoYXJ0cw0KYGBge3J9DQpkZl9jaGFydHNfc2VsIDwtIGRmX2NoYXJ0cyAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHNlbGVjdCggIlRyYWNrX05hbWUiLCAiQXJ0aXN0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICJzdHJlYW1zX2F2ZyIsICJwb3NpdGlvbl9tZWRpYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAic2VtYW5hc19zdW0iLCAicG9wdWxhcmlkYWQiKQ0KDQpnZ3Bsb3QocmVzaGFwZTI6Om1lbHQoZGZfY2hhcnRzX3NlbFssMzo2XSksIGFlcyh2YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbSgpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hcyBvcmlnaW5hbGVzIikNCg0KDQpkZl9jaGFydHNfc2VsJHN0cmVhbXNfYXZnX2NhdCA9IGN1dF9jdWFudGlsZShkZl9jaGFydHNfc2VsJHN0cmVhbXNfYXZnKQ0KZGZfY2hhcnRzX3NlbCRwb3B1bGFyaWRhZF9jYXQgPSBjdXRfY3VhbnRpbGUoZGZfY2hhcnRzX3NlbCRwb3B1bGFyaWRhZCkNCg0KZGZfY2hhcnRzX3NlbCRwb3NpdGlvbl9tZWRpYW5fY2F0ID0gY3V0KGRmX2NoYXJ0c19zZWwkcG9zaXRpb25fbWVkaWFuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHF1YW50aWxlKGRmX2NoYXJ0c19zZWwkcG9zaXRpb25fbWVkaWFuKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZS5sb3dlc3QgPSBULA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCAiTXV5IGFsdGEiLCAiQWx0YSIsICJNZWRpYSIsICJCYWphIikpDQoNCmRmX2NoYXJ0c19zZWwkc2VtYW5hc19zdW1fY2F0ID0gY3V0KGRmX2NoYXJ0c19zZWwkc2VtYW5hc19zdW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDAsIDIsNCwxMywgSW5mKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGUubG93ZXN0ID0gVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJCYWphIiwiTWVkaWEiLCJBbHRhIiwgIk11eSBhbHRhIikpDQoNCg0KIyBmaWx0cm8NCmRmX2NoYXJ0c19jYXQgPC0gZGZfY2hhcnRzX3NlbCAlPiUgDQogIHNlbGVjdCggQXJ0aXN0LCAgVHJhY2tfTmFtZSwgY29udGFpbnMoIl9jYXQiKSAgKQ0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KIyBKT0lOIEZJTkFMDQoNCmRmX2NhdCA8LSBkZl9hdWRpb19mdF9jYXQgJT4lIA0KICByaWdodF9qb2luKCBkZl9jaGFydHNfY2F0ICwNCiAgICAgICAgICAgICAgIGJ5ID0gYygidHJhY2tfbmFtZSIgPSAiVHJhY2tfTmFtZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICJhcnRpc3Rfa2V5IiA9IkFydGlzdCIpKSANCg0KYGBgDQoNCg0KDQoNCiMjIyBWaWVqbw0KYGBge3J9DQojcHJ1ZWJhIGZhbGxpZGENCmF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemVfbm9ybSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGdyb3VwX2J5KGFydGlzdF9uYW1lLCB0cmFja19uYW1lLCBhcnRpc3Rfa2V5KSAlPiUgDQogIG11dGF0ZV9hbGwoIGZ1bmN0aW9uKHgpeyBjdXQoeCwgcXVhbnRpbGUoeCwgcHJvYnMgPSBzZXEoMCwgMSwgMC4yNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksIG5hLnJtID0gVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICkgLCBsYWJlbHMgPSBjKCJNdXkgQWx0YSIsIkFsdGEiICwiTWVkaWEiLCAiQmFqYSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICkgLCBsYWJlbHMgPSBjKCJNdXkgQWx0YSIsIkFsdGEiICwiTWVkaWEiLCAiQmFqYSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApICwgbGFiZWxzID0gYygiTXV5IEFsdGEiICwiQWx0YSIsICJNZWRpYSIsICJCYWphIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIH0NCiAgICAgICAgICAgICAgKQ0KICANCg0KDQojIERpc2NyZXRpemFjaW9uZXMgYmFzYWRhcyBlbiBRdWFudGlsZSAoRlVOQ0lPTkEpDQoNCmxpYnJhcnkoUi5vbykNCg0KY29scyA8LSBuYW1lcyhhdWRpb19mdF90b19kaXNjcmV0aXplX25vcm0pWyFuYW1lcyhhdWRpb19mdF90b19kaXNjcmV0aXplX25vcm0pICVpbiUgYygiYXJ0aXN0X25hbWUiLCJhcnRpc3Rfa2V5IiwgInRyYWNrX25hbWUiKV0NCmRmX251bSA9IGF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemVfbm9ybVssY29sc10NCg0KZGZfY2F0IDwtICBhdWRpb19mdF90b19kaXNjcmV0aXplX25vcm1bLCBjKCJhcnRpc3RfbmFtZSIsImFydGlzdF9rZXkiLCAidHJhY2tfbmFtZSIpXQ0KZm9yKGkgaW4gc2VxX2Fsb25nKGRmX251bSkgKXsNCg0KICBicmVha3MgPXVuaXF1ZShxdWFudGlsZShkZl9udW1bW2ldXSwgcHJvYnMgPSBzZXEoMCwgMSwgMC4zMykgLG5hLnJtID0gVCkpDQogICMgYnJlYWtzID11bmlxdWUocXVhbnRpbGUoZGZfbnVtW1tpXV0sIHByb2JzID0gc2VxKDAsIDEsIDAuMjUpKSkNCiAgIyBicmVha3MgPXF1YW50aWxlKGRmX251bVtbaV1dICwgbmFtZXMgPSBGQUxTRSkNCg0KICBsYWJlbCA9IGludFRvQ2hhcig2NTooNjMrbGVuZ3RoKGJyZWFrcykpKQ0KICAjIGxhYmVsID0gYygiTXV5IEFsdGEiLCAiQWx0YSIsICJNZWRpYSIsICJCYWphIikNCg0KICB4IDwtICAgY3V0KGRmX251bVtbaV1dLCBicmVha3M9YnJlYWtzLA0KICAgICAgICAgICAgbGFiZWxzID0gbGFiZWwgKQ0KICAgICAgICAgICAgIyBsYWJlbHMgID0gYygiTXV5IEFsdGEiLCJBbHRhIiAsIk1lZGlhIiwgIkJhamEiKSApDQoNCiAgZGZfY2F0IDwtICBjYmluZChkZl9jYXQsICB4ICkNCiAgfQ0KDQojIHNldGVvIGRlIG5vbWJyZXMNCm5hbWVzKGRmX2NhdCkgPC0gbmFtZXMoYXVkaW9fZnRfdG9fZGlzY3JldGl6ZV9ub3JtKQ0KDQojIGF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemVfbm9ybSAgJT4lIGZpbHRlcih0cmFja19uYW1lID09Ikp1aWNlIFdSTEQgU3BlYWtzIEZyb20gSGVhdmVuIC0gT3V0cm8iKQ0KIyBkZl9jYXQgICU+JSBmaWx0ZXIodHJhY2tfbmFtZSA9PSJCYWJ5IikNCiMgZGZfY2F0ICAlPiUgZmlsdGVyKHRyYWNrX25hbWUgPT0iZ29vZGJ5ZSIpDQojIGRmX2NhdCAgJT4lIGZpbHRlcih0cmFja19uYW1lID09IkTDgUtJVEkiKQ0KIyBhdWRpb19mdF90b19kaXNjcmV0aXplX25vcm0gICU+JSBmaWx0ZXIodHJhY2tfbmFtZSA9PSJSdWxlIFRoZSBXb3JsZCAoZmVhdC4gQXJpYW5hIEdyYW5kZSkiKQ0KIyBkZl9jaGFydHMgICU+JSBmaWx0ZXIoVHJhY2tfTmFtZSA9PSJSdWxlIFRoZSBXb3JsZCAoZmVhdC4gQXJpYW5hIEdyYW5kZSkiKQ0KDQphdWRpb19mdF90b19kaXNjcmV0aXplX25vcm0gICU+JQ0KICBncm91cF9ieShhcnRpc3RfbmFtZSwgdHJhY2tfbmFtZSkgJT4lDQogIGFycmFuZ2Uoc3RyZWFtc19hdmcpDQogICMgc2xpY2Uod2hpY2gubWF4KGRhbmNlYWJpbGl0eSkpDQogICMgZmlsdGVyKGRhbmNlYWJpbGl0eSA9PSBtYXgoZGFuY2VhYmlsaXR5LCBuYS5ybSA9VCkpDQoNCiNhbmFsaXNpcyBkZSBtaXNzaW5nIHZhbHVlcw0KbWljZTo6bWQucGF0dGVybihkZl9jYXQsIHBsb3QgPSBULCByb3RhdGUubmFtZXMgPSBUKQ0KDQpzdW0oIWNvbXBsZXRlLmNhc2VzKGRmX2NhdCkpDQoNCnN1bW1hcnkoVklNOjphZ2dyKGRmX2NhdCwgc29ydFZhcj0gVCxwbG90PUYpKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KDQojIEpPSU4gRklOQUwNCg0KI2ZhbHRhIHRyYW5zZm9ybWFyIGRmX2NoYXJ0cyAhISENCmpvaW5fYXVkaW9fY2hhcnRzIDwtIGF1ZGlvX2Z0X3RvX2Rpc2NyZXRpemVfbm9ybSAlPiUgDQogIHJpZ2h0X2pvaW4oIGNoYXJ0c19zaW5fc2VzZ29fbm9ybSAsDQogICAgICAgICAgICAgICBieSA9IGMoInRyYWNrX25hbWUiID0gIlRyYWNrX05hbWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAiYXJ0aXN0X2tleSIgPSJBcnRpc3QiKSkgJT4lIA0KICBzZWxlY3QoLWMoImFydGlzdF9rZXkiLCAia2V5X25hbWUiLCAiZXhwbGljaXQiLCAibW9kZV9uYW1lIiAsImtleV9tb2RlIikpICU+JSANCiAgZ3JvdXBfYnkoYXJ0aXN0X25hbWUsIHRyYWNrX25hbWUpICU+JQ0KICBzdW1tYXJpc2VfYWxsKCBmdW5jdGlvbih4KSBtZWFuKHgsIG5hLnJtPSBUKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUgDQogIGZpbHRlcighaXMubmEoYXJ0aXN0X25hbWUpKSAlPiUgDQogIGdyb3VwX2J5KGFydGlzdF9uYW1lLCB0cmFja19uYW1lKSMgJT4lDQogICMgbXV0YXRlX2FsbChmdW5jdGlvbih4KSBzY2FsZXM6OnJlc2NhbGUoeCwgdG89YygwLDEpKSkNCiAgIyBtdXRhdGVfYWxsKGZ1bmN0aW9uKHgpICAoeC1taW4oeCwgbmEucm0gPSBUKSkgLyAobWF4KHgsIG5hLnJtID0gVCktbWluKHgsIG5hLnJtID0gVCkpICkgDQogIA0KYGBgDQoNCg0KDQojIE5vcm1hbGl6YWNpb24gdmllamENCiMjIyBaLVNjb3JlIGRlIFZhcmlhYmxlcyBxdWUgInRpZW5kZW4gYSBsYSBub3JtYWwiDQpgYGB7cn0NCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KIyMgRklMVFJBTU9TIE9VVExJRVJTIFBPUiBaLVNDT1JFIHBhcmEgJ2RhbmNlYWJpbGl0eScsICd0ZW1wbycsICd2YWxlbmNlJw0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KI3otc2NvcmUgcGFyYSB2YXJpYWJsZXMgcXVlIHRpZW5kZW4gYSBsYSBub3JtYWwNCiNmaWx0cm8gZmVhdHVyZXMgbnVtZXJpY29zIA0KDQojZGl2aWRvIGxvcyBmZWF0dXJlcyBwb3Igc3UgZGlzdHJpYnVjacOzbg0KZmVhdHVyZXNfY29udGludWFzX21lZGlhIDwtIGMoJ2RhbmNlYWJpbGl0eScsICd0ZW1wbycsICd2YWxlbmNlJykNCmRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYSA8LSBkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZXNfY29udGludWFzX21lZGlhXQ0KDQojbm9ybWFsaXpvIHogc2NvcmUgY29uIGxhcyB2YXJpYWJsZXMgcXVlIHRpZW5kZW4gYSBsYSBub3JtYWwNCg0KenNjb3JlX2NvbHMgPC0gYygpDQpmb3IoY29sIGluIG5hbWVzKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYSkpew0KICBuYW1lX2NvbCA8LSBwYXN0ZSgnenNjb3JlXycsY29sLCBzZXAgPSAiIikNCiAgenNjb3JlX2NvbHMgPC0gYXBwZW5kKHpzY29yZV9jb2xzLCBuYW1lX2NvbCkNCiAgbWVkaWEgPC0gIG1lYW4oZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhWyxjb2xdKQ0KICBzdGR2IDwtIHNkKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYVssY29sXSkNCiAgZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhWyxuYW1lX2NvbF0gPC0gKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYVssY29sXSAtIG1lZGlhKS9zdGR2DQogIH0NCg0KcGFyKG1mcm93PWMoMSxsZW5ndGgoenNjb3JlX2NvbHMpKSkNCmxhcHBseSh6c2NvcmVfY29scywgZnVuY3Rpb24oY29sKSBib3hwbG90KGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYVssY29sXSx4bGFiPWNvbCkpDQpgYGANCg0KIyMjIEFuYWxpc2lzIGRlIFotU2NvcmUgcG9yIHZhcmlhYmxlDQogDQpEYW5jZWFiaWxpdHkNCg0KYGBge3J9DQojdmFyaWFibGU6IGRhbmNlYWJpbGl0eQ0KDQp1bWJyYWxfenNjb3JlIDwtIDMNCmNvbmRpdGlvbnMgPC0gKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYSR6c2NvcmVfZGFuY2VhYmlsaXR5PiB1bWJyYWxfenNjb3JlKSB8IChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWEkenNjb3JlX2RhbmNlYWJpbGl0eTwgLTEqdW1icmFsX3pzY29yZSkNCmRmX2F1ZGlvX2ZlYXR1cmVzW2NvbmRpdGlvbnMsXSAlPiUNCiAgc2VsZWN0KGFsYnVtX25hbWUsYXJ0aXN0X25hbWUsIGRhbmNlYWJpbGl0eSApICU+JQ0KICBhcnJhbmdlKC1kYW5jZWFiaWxpdHkpDQpgYGANCg0KVGVtcG8NCg0KYGBge3J9DQojdmFyaWFibGU6IFRlbXBvDQoNCnVtYnJhbF96c2NvcmUgPC0gMw0KY29uZGl0aW9ucyA8LSAoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhJHpzY29yZV90ZW1wbz4gdW1icmFsX3pzY29yZSkgfCAoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhJHpzY29yZV90ZW1wbzwgLTEqdW1icmFsX3pzY29yZSkNCmRmX2F1ZGlvX2ZlYXR1cmVzW2NvbmRpdGlvbnMsXSAlPiUNCiAgc2VsZWN0KGFsYnVtX25hbWUsYXJ0aXN0X25hbWUsIHRlbXBvICkgJT4lDQogIGFycmFuZ2UoLXRlbXBvKQ0KYGBgDQoNClZhbGVuY2UNCg0KYGBge3J9DQojdmFyaWFibGU6IHZhbGVuY2UNCnVtYnJhbF96c2NvcmUgPC0gMw0KY29uZGl0aW9ucyA8LSAoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhJHpzY29yZV92YWxlbmNlPiB1bWJyYWxfenNjb3JlKSB8IChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWEkenNjb3JlX3ZhbGVuY2U8IC0xKnVtYnJhbF96c2NvcmUpDQpkZl9hdWRpb19mZWF0dXJlc1tjb25kaXRpb25zLF0gJT4lDQogIHNlbGVjdChhbGJ1bV9uYW1lLGFydGlzdF9uYW1lLCB2YWxlbmNlICkgJT4lDQogIGFycmFuZ2UoLXZhbGVuY2UpDQpgYGANCg0KIyMjIFotU2NvcmUgTW9kaWZpY2FkbyBkZSBWYXJpYWJsZXMgQXNpbWV0cmljYXMNCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojIyBGSUxUUkFNT1MgT1VUTElFUlMgUE9SIFotU0NPUkUgTU9ESUZJQ0FETyBwYXJhICdhY291c3RpY25lc3MnLCAnZHVyYXRpb25fbXMnLCAnZW5lcmd5JywgICdpbnN0cnVtZW50YWxuZXNzJywgJ2xpdmVuZXNzJywgJ2xvdWRuZXNzJywgJ3NwZWVjaGluZXNzJywgJ2NhbnRfbWFya2V0cycNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCmZlYXR1cmVzX2NvbnRpbnVhc19tZWRpYW5hIDwtIGMoJ2Fjb3VzdGljbmVzcycsICdkdXJhdGlvbl9tcycsICdlbmVyZ3knLCAnaW5zdHJ1bWVudGFsbmVzcycsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICdjYW50X21hcmtldHMnKQ0KDQpkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFuYSA8LSBkZl9hdWRpb19mZWF0dXJlc1ssZmVhdHVyZXNfY29udGludWFzX21lZGlhbmFdDQoNCg0KDQp6c2NvcmVtb2RpZl9jb2xzIDwtIGMoKQ0KZm9yKGNvbCBpbiBuYW1lcyhkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFuYSkpew0KICBuYW1lX2NvbCA8LSBwYXN0ZSgnenNjb3JlbW9kaWZfJyxjb2wsIHNlcCA9ICIiKQ0KICB6c2NvcmVtb2RpZl9jb2xzIDwtIGFwcGVuZCh6c2NvcmVtb2RpZl9jb2xzLCBuYW1lX2NvbCkNCiAgbWVkID0gbWVkaWFuKGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hWyxjb2xdLCBuYS5ybSA9IFQpDQogIE1BRCA9IG1lZGlhbihhYnMoZGZfYXVkaW9fZmVhdHVyZXNfenNjb3JlX21lZGlhbmFbLGNvbF0gLSBtZWQpLCBuYS5ybSA9IFQpDQogIGRmX2F1ZGlvX2ZlYXR1cmVzX3pzY29yZV9tZWRpYW5hWywgbmFtZV9jb2xdIDwtIDAuNjc0NSAqIChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFuYVssY29sXSAtIG1lZCkgLyBNQUQNCn0NCg0KDQpwYXIobWZyb3c9Yyg0LDIpKQ0KbGFwcGx5KHpzY29yZW1vZGlmX2NvbHMsIGZ1bmN0aW9uKGNvbCkgYm94cGxvdChkZl9hdWRpb19mZWF0dXJlc196c2NvcmVfbWVkaWFuYVssY29sXSx4bGFiPWNvbCwgaG9yaXpvbnRhbCA9IFQpKQ0KDQpgYGANCg0KDQpSZXZpc2nDs24gVmFyaWFibGUgYEluc3RydW1lbnRhbG5lc3NgDQpgYGB7cn0NCmluc3RydW1lbnRhbG5lc3MgPC0gYygiaW5zdHJ1bWVudGFsbmVzcyIsICJ6c2NvcmVtb2RpZl9pbnN0cnVtZW50YWxuZXNzIikgDQoNCnggPC0gZGZfYXVkaW9fZmVhdHVyZXMkaW5zdHJ1bWVudGFsbmVzcw0KDQpuX2ludGVydiA8LSAxMA0KDQoNCmludGVydmFsb3MgPC0gcm91bmQoc2VxKDAsbWF4KHgpLGJ5PShtYXgoeCktbWluKHgpKS9uX2ludGVydiksMikNCg0KbGFicyA8LSBjKCkNCmZvciAoaSBpbiAxOm5faW50ZXJ2KXsNCmxhYiA8LSBwYXN0ZShpbnRlcnZhbG9zW2ldLGludGVydmFsb3NbaSsxXSwgc2VwPSdcbicpDQpsYWJzIDwtIGFwcGVuZChsYWJzLCBsYWIpDQogICAgDQp9DQoNCmJpbnMgPC0gY3V0KHgsIG5faW50ZXJ2LCBpbmNsdWRlLmxvd2VzdCA9IFRSVUUsIGxhYmVscyA9IGxhYnMpDQoNCmJhcnBsb3QodGFibGUoYmlucykpDQoNCmBgYA0KDQpIYWNlbW9zIEstbWVhbnMgcGFyYSBwb2RlciBkaXNjcmV0aXphciBsYSB2YXJpYWJsZS4gDQoNCmBgYHtyfQ0Kc3NlIDwtIGMoKQ0KZm9yIChrIGluIDI6Nil7DQogIGNsdXN0ZXJzIDwtIGttZWFucyhkZl9hdWRpb19mZWF0dXJlcyRpbnN0cnVtZW50YWxuZXNzLGNlbnRlcnMgPSBrLCBpdGVyLm1heCA9IDEwLCBuc3RhcnQgPSBrKQ0KICBzc2UgPC0gYXBwZW5kKHNzZSwgY2x1c3RlcnMkdG90LndpdGhpbnNzKQ0KfQ0KDQpwbG90KDI6Nixzc2UsIHR5cGUgPSAnbCcsIHhsYWI9J0NhbnRpZGFkIGRlIENsdXN0ZXJzJywgeWxhYj0nU3VtYSBFcnJvciBDdWFkcsOhdGljbycpDQoNCiNrPTMgDQpjbHVzdGVyczMgPC0ga21lYW5zKGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3MsY2VudGVycyA9IDMsIGl0ZXIubWF4ID0gMTAsIG5zdGFydCA9IDMpDQoNCmRmX2F1ZGlvX2ZlYXR1cmVzJGNsdXN0ZXJzIDwtIGZhY3RvcihjbHVzdGVyczMkY2x1c3RlcikNCg0KbGV2IDwtIGxldmVscyhkZl9hdWRpb19mZWF0dXJlcyRjbHVzdGVycykNCg0KbGFicyA8LSBjKCkNCmZvciAoaSBpbiBsZXYpew0KICBtaW4gPC0gbWluKGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3NbZGZfYXVkaW9fZmVhdHVyZXMkY2x1c3RlcnM9PWldKQ0KICBtYXggPC0gbWF4KGRmX2F1ZGlvX2ZlYXR1cmVzJGluc3RydW1lbnRhbG5lc3NbZGZfYXVkaW9fZmVhdHVyZXMkY2x1c3RlcnM9PWldKQ0KICBsYWIgPC0gcGFzdGUobWluLG1heCwgc2VwPScgLSAnKQ0KICBsYWJzIDwtIGFwcGVuZChsYWJzLCBsYWIpDQp9DQoNCmxhYnMNCg0KIyBiYXJwbG90KHRhYmxlKGZhY3RvcihjbHVzdGVyczMkY2x1c3RlcikpLCBsYWJlbHMgPSBsYWJzKQ0KDQoNCg0KYGBgDQoNCg0KI3BydWViYSBpZ2FsIGRlIHRyYW5zZm9ybWFjaW9uIHkgdGVzdCBkZSBub3JtYWxpZGFkDQpgYGB7cn0NCmpvaW5fYXVkaW9fY2hhcnRzWzE6NSwiYWNvdXN0aWNuZXNzIl1eMg0KDQpsaWJyYXJ5KG5vcnRlc3QpDQoNCmxvZzEwKGRmX2NoYXJ0X3dfbHlyaWNzJGFjb3VzdGljbmVzcykNCg0KZm9yIChpIGluIGZlYXR1cmVzX2NvbnRpbnVhcyl7DQogICB4IDwtIGxvZzEwKGRmX2NoYXJ0X3dfbHlyaWNzWyxpXSkNCiAgIHggPC0gc2hhcGlyby50ZXN0KHgpDQogICB6IDwtIHgkcC52YWx1ZQ0KICBwcmludCh6KQ0KICB9DQoNCg0KYGBgDQoNCg0KDQoNCnwjIExZUklDUw0KDQoNCiMjIEZpbHRybyBwb3IgaWRpb21hDQpgYGB7cn0NCmxpYnJhcnkodGV4dGNhdCkNCg0KZGZfbHlyaWNzIDwtIHJlYWQuY3N2KCJkYXRhL2RmX2x5cmljcy5jc3YiKSAlPiUgDQogIHNlbGVjdCgtWCkNCg0KZGZfbHlyaWNzX3VuaWNhcyA8LSBkZl9seXJpY3MgJT4lIA0KICBkaXN0aW5jdChhcnRpc3RfbmFtZSwgdHJhY2tfbmFtZSwgbHlyaWNzLCAua2VlcF9hbGwgPSBUKQ0KDQojam9pbg0Kam9pbl9seV9mdCA9IG1lcmdlKHggPSBkZl9jYXQsIA0KICAgICAgeSA9IGRmX2x5cmljc191bmljYXMsDQogICAgICBieS54ID0gYygiYXJ0aXN0X25hbWUiLCJ0cmFja19uYW1lIiksDQogICAgICBieS55ID0gYygiYXJ0aXN0X25hbWUiLCJ0cmFja19uYW1lIikpICU+JSANCiAgZGlzdGluY3QobHlyaWNzLCAua2VlcF9hbGwgPSBUKQ0KDQojZmlsdHJvIGRlIGlkaW9tYQ0Kc3BhX2x5cmljcyA9IGpvaW5fbHlfZnRbdGV4dGNhdChqb2luX2x5X2Z0JGx5cmljcyk9PSJzcGFuaXNoIiwgXSANCiAgICAgICAgICAgICAgICAgICAgICAgICNjKCJhcnRpc3RfbmFtZSIsICJ0cmFja19uYW1lIiwgImx5cmljcyIpXSANCmVuX2x5cmljcyA9IGpvaW5fbHlfZnRbdGV4dGNhdChqb2luX2x5X2Z0JGx5cmljcyklaW4lIGMoImVuZ2xpc2giLCAic2NvdHMiKSwgXQ0KICAgICAgICAgICAgICAgICAgICAgICAjYygiYXJ0aXN0X25hbWUiLCAidHJhY2tfbmFtZSIsICJseXJpY3MiKV0gDQoNCg0KYGBgDQoNCg0KYGBge3J9DQojIHRhYmxhIGNvbnRpbmdlbmNpYSBkZSBpZGlvbWFzDQojIGlkaW9tYXMgPSB0ZXh0Y2F0KGRmX2x5cmljc191bmljYXMkbHlyaWNzKQ0KIyBzb3J0KHRhYmxlKGlkaW9tYXMpLCBkZWNyZWFzaW5nID0gVCkNCmBgYA0KDQojIyMgbGltcGllemEgaW5nbGVzDQpgYGB7cn0NCiNmdW5jaW9uZXMNCiNmdW5jaW9uIHBhcmEgY29ycmVnaXIgcGFsYWJyYXMNCmRlY29udHJhY3RlZCA9IGZ1bmN0aW9uKHR4dCl7DQogIHR4dCA9IGdzdWIoImFpbid0IiwgImFpbnQiLCB0eHQpDQogIHR4dCA9IGdzdWIoIndhbm5hIiwgIndhbnQgdG8iLCB0eHQpDQogIHR4dCA9IGdzdWIoImdvbm5hIiwgImdvaW5nIHRvIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJ3b24ndCIsICJ3aWxsIG5vdCIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiYydtb24iLCAiY29tZSBvbiIsIHR4dCkNCiAgdHh0ID0gZ3N1YigibGV0J3MiLCAibGV0IHVzIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCdzIiwgIiBpcyIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwndCIsICIgbm90IiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXCdsbCIsICIgd2lsbCIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwnbSIsICIgYW0iLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcJ3JlIiwgIiBhcmUiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcJ2QiLCAiIGhhZCIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwndmUiLCAiIGhhdmUiLCB0eHQpDQogIHR4dCA9IGdzdWIoImNvdWxkbiIsICJjb3VsZCIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiZG9uIiwgImRvIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJkb2VzbiIsICJkb2VzIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJpc24iLCAiaXMiLCB0eHQpDQogIHR4dCA9IGdzdWIoIm11c3RuIiwgIm11c3QiLCB0eHQpDQogIHR4dCA9IGdzdWIoInNob3VsZG4iLCAic2hvdWxkIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJ3YXNuIiwgIndhcyIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwnbiIsICIgYW5kICIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxeJ24nIiwgIiBhbmQgIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXF5uJyIsICIgYW5kICIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFwnY2F1c2UiLCAiIGJlY2F1c2UiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcYid1XFxiIiwgIiB5b3UiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcYnUnXFxiIiwgIiB5b3UgIiwgdHh0KQ0KICB0eHQgPSBnc3ViKCJcXGJ1XFxiIiwgInlvdSIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxpbiciLCAiaW5nIiwgdHh0KQ0KICByZXR1cm4odHh0KQ0KfQ0KDQoNCiNGdW5jacOzbiBwYXJhIGxpbXBpYXIuIA0KdGV4dF9jbGVhbmluZyA9IGZ1bmN0aW9uKHR4dCwgbGFuZ3VhZ2Upew0KICB0eHQgPSBzdWIoJ14uKz9cXFsuKj9cXF0nLCIiLCB0eHQpICNvaw0KICB0eHQgPSBzdWIoIk1vcmUgb24gR2VuaXVzLioiLCIiLCB0eHQpDQogIHR4dCA9IGdzdWIoJ1xcWy4qP1xcXScsICcnLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcbiIsIiAiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlsoKV0iLCAiICIsIHR4dCkNCiAgdHh0ID0gZ3N1YigiKFthLXpdKykoW0EtWl0rKSIsICJcXDEgXFwyIiwgdHh0KQ0KICB0eHQgPSB0b2xvd2VyKHR4dCkNCiAgdHh0ID0gZ3N1YigiXFxkIiwgIiAiLCB0eHQpDQogIGlmKGxhbmd1YWdlID09ICJlbiIpew0KICAgIHR4dCA9IGRlY29udHJhY3RlZCh0eHQpDQogIH1lbHNlIGlmIChsYW5ndWFnZSA9PSAiZXMiKXsNCiAgICB0eHQgPSBnc3ViKCLDsWkiLCJuaSIsIHR4dCkNCiAgICB0eHQgPSBnc3ViKCfDsScsICduaScsIHR4dCkNCiAgICB0eHQgPSBzdHJpX3RyYW5zX2dlbmVyYWwodHh0LCJMYXRpbi1BU0NJSSIpDQogIH0NCiAgdHh0ID0gZ3N1YigiXFxXK1xcYiIsICIgIiwgdHh0KQ0KICByZXR1cm4odHh0KQ0KfQ0KDQojbGltcGlvIGxhcyBsZXRyYXMgZW4gaW5nbGVzDQplbl9seXJpY3MkbHlyaWNzX2NsZWFuaW5nID0gdGV4dF9jbGVhbmluZyhlbl9seXJpY3MkbHlyaWNzLCBsYW5ndWFnZSA9ICJlbiIpDQpzcGFfbHlyaWNzJGx5cmljc19jbGVhbmluZyA9IHRleHRfY2xlYW5pbmcoc3BhX2x5cmljcyRseXJpY3MsIGxhbmd1YWdlID0gImVzIikNCg0KDQojd3JpdGUuY3N2KGVuX2x5cmljcywgImRhdGEvZW5fbHlyaWNzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQoNCmBgYA0KDQojIyBTVE9QV09SRFMNCg0KIyMjIEluZ2zDqXMNCmBgYHtyfQ0KDQojRWwgd29yZF9jb3VudF9kZiBzZSByZWFsaXphIGNvbiBQeXRob24gKGVqZWN1dGFyIGVuX2x5cmljc193b3JkX2NvdW4pDQp3b3JkX2NvdW50c19kZiA8LSByZWFkLmNzdigiZGF0YS9lbl9seXJpY3Nfd29yZF9jb3VudC5jc3YiKQ0KDQpgYGANCg0KYGBge3J9DQp4IDwtIDE6bnJvdyh3b3JkX2NvdW50c19kZikNCmYgPC0gd29yZF9jb3VudHNfZGYkY291bnRzDQpwbG90KHgsZiwgdHlwZT0ibCIsDQogICAgICAgICAgbG9nID0gJ3h5JywNCiAgICAgICAgICBjb2w9ImJsdWUiLA0KICAgICAgICAgIGx3ZD0zLA0KICAgICAgICAgIHlsYWIgPSAiRnJlY3VlbmNpYSBBYnNvbHV0YSIsDQogICAgICAgICAgeGxhYiA9ICIiLA0KICAgICAgICAgIG1haW49IkZyZWN1ZW5jaWEgZGUgUGFsYWJyYXMgLSBMZXkgZGUgWmlwZiIpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHN0b3B3b3JkcykNCg0KdGhyZXNob2xkIDwtIDEwMDANCg0KeCA8LSBhcy52ZWN0b3Iod29yZF9jb3VudHNfZGZbd29yZF9jb3VudHNfZGYkY291bnRzPnRocmVzaG9sZCxdJHdvcmQpDQoNCm5vdF9yZW1vdmVfbGlzdCA9IGMoImJpdGNoIiwiZnVjayIsImxvdmUiLCJiYWJ5IiwibmlnZ2EiLCJmZWVsIiwgImdpcmwiLCAic2hpdCIpDQoNCnRvcF9zdG9wd29yZHMgPC0gc2V0ZGlmZih4LCBub3RfcmVtb3ZlX2xpc3QpDQoNCnNtYXJ0X3N0b3B3b3JkcyA8LSBzdG9wd29yZHMoImVuIiwgc291cmNlID0gInNtYXJ0IikNCm15X2VuZ19zdG9wd29yZHMgPC0gdW5pcXVlKGFwcGVuZChzbWFydF9zdG9wd29yZHMsIHRvcF9zdG9wd29yZHMpKQ0KDQpgYGANCg0KDQoNCiMjIyBFc3Bhw7FvbA0KDQpgYGB7cn0NCm15X3NwYV9zdG9wd29yZHMgPC0gdW5pcXVlKHRleHRfY2xlYW5pbmcoc3RvcHdvcmRzKCJlcyIsIHNvdXJjZSA9ICJzdG9wd29yZHMtaXNvIiksIGxhbmd1YWdlID0gImVzIikpICANCmBgYA0KDQoNCiMjIyBCb3JybyBTdG9wd29yZHMNCmBgYHtyfQ0KZGVsX3N0b3B3b3JkcyA9IGZ1bmN0aW9uKHR4dCwgc3RvcHdvcmRfbGlzdCl7DQogIHJlbW92ZV9yZWdleCA9IHBhc3RlKCJcXGIoIiwgcGFzdGUoc3RvcHdvcmRfbGlzdCwgY29sbGFwc2UgPSAifCIpLCIpXFxXIiwgc2VwPSIiKQ0KICB0eHQgPSBnc3ViKHJlbW92ZV9yZWdleCwgIiAiLCB0eHQpDQogIHR4dCA9IGdzdWIoIlxcVytcXGIiLCAiICIsIHR4dCkNCiAgcmV0dXJuKHR4dCkNCn0NCg0Kc3BhX2x5cmljcyRzaW5fc3RvcHdvcmRzIDwtIGRlbF9zdG9wd29yZHMoc3BhX2x5cmljcyRseXJpY3NfY2xlYW5pbmcsIG15X3NwYV9zdG9wd29yZHMpDQplbl9seXJpY3Mkc2luX3N0b3B3b3JkcyA8LSBkZWxfc3RvcHdvcmRzKGVuX2x5cmljcyRseXJpY3NfY2xlYW5pbmcsIG15X2VuZ19zdG9wd29yZHMpDQoNCmBgYA0KDQoNCg0KIyMgRElDQ0lPTkFSSU8gREUgTUFMQVMgUEFMQUJSQVMNCg0KIyMjIEVzcGHDsW9sDQpgYGB7cn0NCiNEaWNjaW9uYXJpbyBlc3Bhw7FvbA0KbWFsYXNfcGFsYWJyYXNfMSA8LSByZWFkX2NzdigiZGF0YS9tYWxhc19wYWxhYnJhcy50eHQiLCANCiAgICBjb2xfbmFtZXMgPSBGQUxTRSkNCg0KbWFsYXNfcGFsYWJyYXNfMiA8LSByZWFkX2NzdigiZGF0YS9tYWxhc19wYWxhYnJhc190cmFuc2xhdGUudHh0IiwgDQogICAgY29sX25hbWVzID0gRkFMU0UpDQoNCm1hbGFzX3BhbGFicmFzXzMgPC0gcmVhZF9jc3YoImRhdGEvbWFsYXNfcGFsYWJyYXNfd2lraS50eHQiLCANCiAgICBjb2xfbmFtZXMgPSBGQUxTRSkgJT4lIA0KICBzZWxlY3QoWDEpDQoNCm1hbGFzX3BhbGFicmFzXzQgPC0gcmVhZF9jc3YoImRhdGEvcGFsYWJyYXNfcHJvZmFuYXNfZXMudHh0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF9uYW1lcyA9IEZBTFNFKQ0KDQptYWxhc19wYWxhYnJhcyA8LSByYmluZChtYWxhc19wYWxhYnJhc18xLCBtYWxhc19wYWxhYnJhc18yLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWFsYXNfcGFsYWJyYXNfMywgbWFsYXNfcGFsYWJyYXNfNCkNCg0KDQojSGFjZXIgdW5pcXVlIHBhcmEgZWxpbWluYXIgbGFzIHJlcGV0aWRhcw0KDQptYWxhc19wYWxhYnJhcyRsaW1waWFzID0gdGV4dF9jbGVhbmluZyhtYWxhc19wYWxhYnJhcyRYMSwgbGFuZ3VhZ2U9ImVzIikNCmBgYA0KDQojIyMgSW5nbMOpcw0KYGBge3J9DQojR2VuZXJvIGxpc3RhIGRlIG1hbGFzIHBhbGFicmFzDQpyYWNpc3Rfd29yZHMgPC0gdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X3JhY2lzdCkpDQoNCmJpZ2xvdSA8LSByZWFkLmNzdigiaHR0cHM6Ly93d3cuY3MuY211LmVkdS9+YmlnbG91L3Jlc291cmNlcy9iYWQtd29yZHMudHh0IiwgaGVhZGVyPUZBTFNFLCBjb2wubmFtZXMgPSBjKCJ3b3JkcyIpKQ0KYGBgDQoNCg0KIyMgTUFUUklaIFRFUk1JTk8gRE9DVU1FTlRPDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIyMjIyMjIEdlbmVyYWNpw7NuIGRlIGxhIE1hdHLDrXogVMOpcm1pbm8tRG9jdW1lbnRvIGRlbCBjb3JwdXMgIyMjIyMjIw0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiNmdW5jacOzbg0KY29ycHVzLnBybzJ0ZG0gPC0gZnVuY3Rpb24oY29ycHVzLCBwb25kZXJhY2lvbiwgbl90ZXJtcyl7DQogICNjb3JwdXMNCiAgDQogIA0KICAjbWF0cml6IFREIA0KICBkdG0gPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gbGlzdCh3ZWlnaHRpbmcgPSBwb25kZXJhY2lvbikpDQogIG1hdHJpel90ZCA8LSBhcy5tYXRyaXgoZHRtKQ0KICANCiAgDQogICMgQ2FsY3VsYW1vcyBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgdMOpcm1pbm8gZW4gZWwgY29ycHVzDQogIGZyZXFfdGVybSA8LSBoZWFkKHNvcnQocm93U3VtcyhtYXRyaXpfdGQpLGRlY3JlYXNpbmc9VFJVRSksIG5fdGVybXMpDQogIA0KICAjbWF0cml6IHRyYW5zcHVlc3RhIGRlIGxvcyBuX3Rlcm1zIG1hcyBmcmVjdWVudGVzDQogIG1hdHJpel9uZiA8LSB0KG1hdHJpel90ZFtzb3J0KG5hbWVzKGZyZXFfdGVybSkpLCBdKQ0KICANCiAgI3Bhc2FqZSBhIGJpbmFyaW8NCiAgbWF0cml6X25mW21hdHJpel9uZj4wXSA8LSAxDQogIA0KICByZXR1cm4obWF0cml6X25mKQ0KICANCiAgfQ0KDQojIGluZ2xlcyAgDQpjb3JwdXNfZW5nID0gQ29ycHVzKFZlY3RvclNvdXJjZShlbmMydXRmOChlbl9seXJpY3Mkc2luX3N0b3B3b3JkcykpKQ0KbWF0cml6IDwtIGNvcnB1cy5wcm8ydGRtKGNvcnB1cyA9IGNvcnB1c19lbmcsIHBvbmRlcmFjaW9uPSAid2VpZ2h0VGYiLG5fdGVybXM9IDE1MCkNCmRmX3RtIDwtIGFzLmRhdGEuZnJhbWUobWF0cml6KQ0KDQojZXNwYcOxb2wNCmNvcnB1c19lc3AgPSBDb3JwdXMoVmVjdG9yU291cmNlKGVuYzJ1dGY4KHNwYV9seXJpY3Mkc2luX3N0b3B3b3JkcykpKQ0KbWF0cml6X2VzcCA8LSBjb3JwdXMucHJvMnRkbShjb3JwdXMgPSBjb3JwdXNfZXNwLCBwb25kZXJhY2lvbj0gIndlaWdodFRmIixuX3Rlcm1zPSAxNTApDQpkZl90bV9lc3AgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXpfZXNwKQ0KYGBgDQoNCg0KIyMgSk9JTiBDQVRFR09SSUNBUyBZIExZUklDUw0KIyMjIEluZ2zDqXMNCmBgYHtyfQ0KDQojIyBKb2luIG1hdHJpeiBkZSBwYWxhYnJhcyBjb24gYXJ0aXN0YSB5IHRyYWNrDQpkZl9seV9mZWF0IDwtICBjYmluZChlbl9seXJpY3NbLCAxOjE5XSwgZGZfdG0pDQoNCm5yb3coZGZfdG0pDQpucm93KGVuX2x5cmljcykgIyAxMDU1IGluc3RhbmNpYXMNCm5yb3coZGZfbHlfZmVhdCkgI2pvaW5lYW4gODY5DQpuYS5vbWl0KGRmX2x5X2ZlYXQpDQoNCm1pY2U6Om1kLnBhdHRlcm4oZGZfbHlfZmVhdFssIDE1MzpuY29sKGRmX2x5X2ZlYXQpXSwgcm90YXRlLm5hbWVzID0gVCkNCg0KZmlsdGVyIDwtICFuYW1lcyhkZl9seV9mZWF0KSAlaW4lIGMoImFydGlzdF9uYW1lIiwgInRyYWNrX25hbWUiICkNCg0KZGZfbHlfZmVhdF9vayA8LSBkZl9seV9mZWF0WywgZmlsdGVyXQ0KIyBkZl9seV9mZWF0X29rID0gZGZfbHlfZmVhdF9va1ssIC0od2hpY2goY29sU3VtcyhkZl9seV9mZWF0X29rKSA9PSAwKSldDQoNCiMgY29sU3VtcyhkZl9seV9mZWF0X29rKQ0KDQpoZWFkKGRmX2x5X2ZlYXRfb2ssIDMpDQpoZWFkKGRmX2x5X2ZlYXQsIDMpDQoNCg0KZGZfbHlfZmVhdCRpZCA9IDE6bnJvdyhkZl9seV9mZWF0KQ0KDQpkZl9tZWx0IDwtIHJlc2hhcGUyOjptZWx0KGRhdGEgPSBkZl9seV9mZWF0Wyw0Om5jb2woZGZfbHlfZmVhdCldLCBpZC52YXJzID0gYygiaWQiKSkgICU+JQ0KICBhcnJhbmdlKGlkKQ0KDQpkZl9tZWx0IDwtIGRmX21lbHRbZGZfbWVsdCR2YWx1ZSAhPSAwLF0NCg0KZGZfbWVsdF90eHQgPC0gZGZfbWVsdFtkZl9tZWx0JHZhbHVlID09IDEsXQ0KZGZfbWVsdF9jYXQgPC0gZGZfbWVsdFtkZl9tZWx0JHZhbHVlICE9IDEsXQ0KDQpkZl9tZWx0X2NhdCR2YXJpYWJsZSA9ICBwYXN0ZTAoZGZfbWVsdF9jYXQkdmFyaWFibGUsICI9IiwgYXMuY2hhcmFjdGVyKGRmX21lbHRfY2F0JHZhbHVlKSkNCg0KDQpoZWFkKGRmX21lbHRfdHh0ICkNCmRpbShkZl9tZWx0X3R4dCApDQoNCiNkZW5vbWlubyBhIGxvcyB0w6lybWlub3MgcHJvZmFub3MNCmRmX21lbHRfdHh0IDwtIGRmX21lbHRfdHh0ICU+JSANCiAgbXV0YXRlKHZhcmlhYmxlID0gY2FzZV93aGVuKGFzLmNoYXJhY3Rlcih2YXJpYWJsZSkgJWluJSBiaWdsb3Ukd29yZHMgfg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIlBST0ZBTkVfIiwgYXMuY2hhcmFjdGVyKHZhcmlhYmxlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIodmFyaWFibGUpICVpbiUgIHJhY2lzdF93b3JkcyB+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiUkFDSVNUXyIsIGFzLmNoYXJhY3Rlcih2YXJpYWJsZSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVCB+IHBhc3RlMCgiVEVSTV8iLCBhcy5jaGFyYWN0ZXIodmFyaWFibGUpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSAgDQogICAgICAgICApDQoNCmRmX21lbHRfdHh0ICU+JSBmaWx0ZXIoc3RhcnRzV2l0aCh2YXJpYWJsZSwgIlBST0YiKSkNCg0KDQojIGRmX21lbHRfdHh0W2RmX21lbHRfdHh0JHZhcmlhYmxlICVpbiUgYmlnbG91JHdvcmRzLF0NCmBgYA0KDQojIyMgRXNwYcOxb2wNCmBgYHtyfQ0KZGZfbHlfZmVhdF9lc3AgPC0gY2JpbmQoc3BhX2x5cmljc1ssIDE6MTldLCBkZl90bV9lc3ApDQoNCm5yb3coc3BhX2x5cmljcykNCm5yb3coZGZfbHlfZmVhdF9lc3ApDQpuYS5vbWl0KGRmX2x5X2ZlYXRfZXNwKQ0KDQptaWNlOjptZC5wYXR0ZXJuKGRmX2x5X2ZlYXRfZXNwWywgMTUzOm5jb2woZGZfbHlfZmVhdF9lc3ApXSwgcm90YXRlLm5hbWVzID0gVCkNCg0KDQpkZl9seV9mZWF0X29rX2VzcCA8LSBkZl9seV9mZWF0X2VzcFssIGZpbHRlcl0NCg0KZGZfbHlfZmVhdF9va19lc3AkaWQgPSAxOm5yb3coZGZfbHlfZmVhdF9va19lc3ApDQoNCmRmX21lbHRfZXNwIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEgPSBkZl9seV9mZWF0X29rX2VzcFssNDpuY29sKGRmX2x5X2ZlYXRfb2tfZXNwKV0sIGlkLnZhcnMgPSBjKCJpZCIpKSAgJT4lDQogIGFycmFuZ2UoaWQpDQoNCmRmX21lbHRfZXNwIDwtIGRmX21lbHRfZXNwW2RmX21lbHRfZXNwJHZhbHVlICE9IDAsXQ0KDQpkZl9tZWx0X2VzcF90eHQgPC0gZGZfbWVsdF9lc3BbZGZfbWVsdF9lc3AkdmFsdWUgPT0gMSxdDQpkZl9tZWx0X2VzcF9jYXQgPC0gZGZfbWVsdF9lc3BbZGZfbWVsdF9lc3AkdmFsdWUgIT0gMSxdDQoNCmRmX21lbHRfZXNwX2NhdCR2YXJpYWJsZSA9ICBwYXN0ZTAoZGZfbWVsdF9lc3BfY2F0JHZhcmlhYmxlLCAiPSIsIGFzLmNoYXJhY3RlcihkZl9tZWx0X2VzcF9jYXQkdmFsdWUpKQ0KDQoNCiNkZW5vbWlubyBhIGxvcyB0w6lybWlub3MgcHJvZmFub3MNCmRmX21lbHRfZXNwX3R4dCA8LSBkZl9tZWx0X2VzcF90eHQgJT4lIA0KICBtdXRhdGUodmFyaWFibGUgPSBjYXNlX3doZW4oYXMuY2hhcmFjdGVyKHZhcmlhYmxlKSAlaW4lIG1hbGFzX3BhbGFicmFzJGxpbXBpYXMgfg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIlBST0ZBTkVfIiwgYXMuY2hhcmFjdGVyKHZhcmlhYmxlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUIH4gcGFzdGUwKCJURVJNXyIsIGFzLmNoYXJhY3Rlcih2YXJpYWJsZSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApICANCiAgICAgICAgICkNCg0KDQpkZl9tZWx0X3R4dF90b19ydWxzX2VzcCA8LSByYmluZChkZl9tZWx0X2VzcF90eHQsIGRmX21lbHRfZXNwX2NhdCkNCg0KZGZfbWVsdF90eHRfdG9fcnVsc19lc3AgPC0gbmEub21pdChkZl9tZWx0X3R4dF90b19ydWxzX2VzcFssLWMoMyldKQ0KbmFtZXMoZGZfbWVsdF90eHRfdG9fcnVsc19lc3AgKSA9IGMoIlRJRCIsICJpdGVtIikNCg0KDQpgYGANCg0KIyBSRUdMQVMNCiMjIEluZ2xlcw0KYGBge3J9DQpkZl9tZWx0X3R4dF90b19ydWxzIDwtIHJiaW5kKGRmX21lbHRfdHh0LCBkZl9tZWx0X2NhdCkNCg0KZGZfbWVsdF90eHRfdG9fcnVscyA8LSBuYS5vbWl0KGRmX21lbHRfdHh0X3RvX3J1bHNbLC1jKDMpXSkNCm5hbWVzKGRmX21lbHRfdHh0X3RvX3J1bHMgKSA9IGMoIlRJRCIsICJpdGVtIikNCg0KDQp3cml0ZS50YWJsZShkZl9tZWx0X3R4dF90b19ydWxzLCBmaWxlPSJkYXRhL3RyYW5zYWNjaW9uc19seXJpY3NfZmVhdHVyZXMudHh0Iiwgcm93Lm5hbWVzID0gRikNCg0KIyBSZWdsYXMNCiMgY2hlcXVlYXIgbmFuJ3MNCmx5cmljc190cmFucyA8LSByZWFkLnRyYW5zYWN0aW9ucygiZGF0YS90cmFuc2FjY2lvbnNfbHlyaWNzX2ZlYXR1cmVzLnR4dCIsIGZvcm1hdCA9ICJzaW5nbGUiLCBjb2xzID0gYygxLDIpKQ0KDQphcnVsZXM6Omluc3BlY3QoaGVhZChseXJpY3NfdHJhbnMsIDMpKQ0KDQpzdW1tYXJ5KGx5cmljc190cmFucykNCnJlZ2xhcyA8LSBhcHJpb3JpKGx5cmljc190cmFucywgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0PTAuMDEsDQogICAgICAgICAgICAgICAgICAgIGNvbmZpZGVuY2UgPSAwLjEsIHRhcmdldCAgPSAicnVsZXMiICApKQ0KDQpyZWdsYXNfc3ViIDwtIHN1YnNldChyZWdsYXMsIHN1YnNldCA9IGxocyAlcGluJSAiUFJPRl8iKQ0KYXJ1bGVzOjppbnNwZWN0KGhlYWQoc29ydChyZWdsYXNfc3ViLCBieSA9ICJsaWZ0IiwgZGVjcmVhc2luZyA9IFQpLDUpKQ0KDQpgYGANCg0KIyMgSW5nbMOpcw0KDQojIyBDYXJnYSBkZSBJZ2FsIChVTklGSUNBUikNCmBgYHtyfQ0KZGZfbHlyaWNzX3VuaWNhcyA8LSBkZl9seXJpY3MgJT4lIGRpc3RpbmN0KGFydGlzdF9uYW1lLCB0cmFja19uYW1lLCBseXJpY3MpDQpucm93KGRmX2x5cmljc191bmljYXMpDQoNCmRmX2NoYXJ0X3dfbHlyaWNzIDwtIG1lcmdlKGpvaW5fYXVkaW9fY2hhcnRzLCBkZl9seXJpY3NfdW5pY2FzLCBieS54ID0gYygiYXJ0aXN0X25hbWUiLCJ0cmFja19uYW1lIiksIGJ5Lnk9IGMoImFydGlzdF9uYW1lIiwidHJhY2tfbmFtZSIpLCBhbGwueD1UUlVFLCBhbGwueSA9IEZBTFNFKQ0KDQpkZl9jaGFydF93X2x5cmljcyA8LSBkZl9jaGFydF93X2x5cmljc1shaXMubmEoZGZfY2hhcnRfd19seXJpY3MkbHlyaWNzKSxdDQoNCmBgYA0KDQoNCiMjIEV4cGxpY2l0DQoNCiMjIyBjb250YXIgbWFsYXMgcGFsYWJyYXMgKFBhcnRlIGRlIElnYWw6IFVOSUZJQ0FSKQ0KYGBge3J9DQoNCmJhZF93b3JkcyA8LSBjKCkNCmJhZF93b3JkcyA8LSBhcHBlbmQoYmFkX3dvcmRzLCB1bmlxdWUodG9sb3dlcihsZXhpY29uOjpwcm9mYW5pdHlfemFjX2FuZ2VyKSkpDQpiYWRfd29yZHMgPC0gYXBwZW5kKGJhZF93b3JkcywgdW5pcXVlKHRvbG93ZXIobGV4aWNvbjo6cHJvZmFuaXR5X2FsdmFyZXopKSkNCmJhZF93b3JkcyA8LSBhcHBlbmQoYmFkX3dvcmRzLCB1bmlxdWUodG9sb3dlcihsZXhpY29uOjpwcm9mYW5pdHlfYXJyX2JhZCkpKQ0KYmFkX3dvcmRzIDwtIGFwcGVuZChiYWRfd29yZHMsIHVuaXF1ZSh0b2xvd2VyKGxleGljb246OnByb2Zhbml0eV9yYWNpc3QpKSkNCmJhZF93b3JkcyA8LSBhcHBlbmQoYmFkX3dvcmRzLCB1bmlxdWUodG9sb3dlcihsZXhpY29uOjpwcm9mYW5pdHlfYmFubmVkKSkpDQoNCmJhZF93b3JkcyA8LSB1bmlxdWUoYmFkX3dvcmRzKQ0KDQoNCmNvbnRhcl9iYWRfd29yZHMgPC0gZnVuY3Rpb24oeCl7DQogIHggPC0gcHJvZmFuaXR5KHgscHJvZmFuaXR5X2xpc3QgPSBiYWRfd29yZHMpDQogIHEgPC0gc3VtKHgkcHJvZmFuaXR5X2NvdW50KQ0KICByZXR1cm4gKHEpDQogIH0NCmRmX2NoYXJ0X3dfbHlyaWNzJGNhbnRfYmFkX3dvcmRzIDwtIHNhcHBseShkZl9jaGFydF93X2x5cmljc1ssImx5cmljcyJdLCBjb250YXJfYmFkX3dvcmRzKQ0KDQoNCmRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQgPC0gZGZfY2hhcnRfd19seXJpY3NbZGZfY2hhcnRfd19seXJpY3MkZXhwbGljaXQ9PVRSVUUgJiBkZl9jaGFydF93X2x5cmljcyRjYW50X2JhZF93b3JkcyA+IDAsIF0NCg0KaGlzdChkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0JGNhbnRfYmFkX3dvcmRzKQ0KDQoNCiNjcmVvIHZhcnMgY2F0ZWfDs3JpY2FzDQpkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0JG5pdmVsX3B1dGVhZGEgPC0gY3V0KGRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQkY2FudF9iYWRfd29yZHMsIGJyZWFrcyA9IGMoMCwxMCwyMCw1MCxJbmYpLCBsYWJlbHM9YygiYmFqbyIsInBvY28iLCJhbHRvIiwibXV5X2FsdG8iKSkNCg0KZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCRuaXZlbF9yYW5raW5nIDwtIGN1dChkZl9jaGFydF93X2x5cmljc19vbmx5X2V4cGxpY2l0JHBvc2l0aW9uX2F2ZywgYnJlYWtzID0gYygxLDEwMCxJbmYpLCBsYWJlbHM9YygiMWExMDAiLCIxMDBhMjAwIikpDQoNCmRmX2NoYXJ0X3dfbHlyaWNzX29ubHlfZXhwbGljaXQkbml2ZWxfcG9wdWxhcmlkYWQgPC0gY3V0KHNxcnQoZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCRjYW50X2JhZF93b3JkcyksIGJyZWFrcyA9IGMoMCwxMCwyMCw1MCxJbmYpLCBsYWJlbHM9YygiYmFqbyIsInBvY28iLCJhbHRvIiwibXV5X2FsdG8iKSkNCg0KdHJhbnNhY3Rpb25zIDwtIGFzKGFzLmRhdGEuZnJhbWUoYXBwbHkoZGZfY2hhcnRfd19seXJpY3Nfb25seV9leHBsaWNpdCwgMiwgYXMuZmFjdG9yKSksICJ0cmFuc2FjdGlvbnMiKQ0KcnVsZXMgPSBhcHJpb3JpKHRyYW5zYWN0aW9ucywgcGFyYW1ldGVyPWxpc3QodGFyZ2V0PSJydWxlcyIsIGNvbmZpZGVuY2U9MC4yNSwgc3VwcG9ydD0wLjEpKQ0KcnVsZXMuc3ViIDwtIHN1YnNldChydWxlcywgc3Vic2V0ID0gbGhzICVwaW4lICJuaXZlbF9wdXRlYWRhIiAmIHJocyAlcGluJSAibml2ZWxfcmFua2luZyIpDQppbnNwZWN0KGhlYWQoc29ydChydWxlcy5zdWIsIGJ5ID0gImxpZnQiLCBkZWNyZWFzaW5nID0gVFJVRSksMTApKQ0KDQojIGRpc2NyZXRpemFjaW9uIGNvbnRpbnVhcyB5IHNlbGVjY2lvbiBkZSB2YXJpYWJsZXMNCiMgaWRlbnRpZmljYXIgcGFsYWJyYXMgZXhwbMOtdA0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpgYGANCg0KDQoNCg==